editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::CompletionParams;
   38use multi_buffer::{
   39    IndentGuide, MultiBufferFilterMode, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47};
   48use serde_json::{self, json};
   49use settings::{
   50    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   51    IndentGuideColoring, ProjectSettingsContent, SearchSettingsContent,
   52};
   53use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   54use std::{
   55    iter,
   56    sync::atomic::{self, AtomicUsize},
   57};
   58use test::build_editor_with_project;
   59use text::ToPoint as _;
   60use unindent::Unindent;
   61use util::{
   62    assert_set_eq, path,
   63    rel_path::rel_path,
   64    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   65    uri,
   66};
   67use workspace::{
   68    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   69    OpenOptions, ViewId,
   70    invalid_item_view::InvalidItemView,
   71    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   72    register_project_item,
   73};
   74
   75fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   76    editor
   77        .selections
   78        .display_ranges(&editor.display_snapshot(cx))
   79}
   80
   81#[gpui::test]
   82fn test_edit_events(cx: &mut TestAppContext) {
   83    init_test(cx, |_| {});
   84
   85    let buffer = cx.new(|cx| {
   86        let mut buffer = language::Buffer::local("123456", cx);
   87        buffer.set_group_interval(Duration::from_secs(1));
   88        buffer
   89    });
   90
   91    let events = Rc::new(RefCell::new(Vec::new()));
   92    let editor1 = cx.add_window({
   93        let events = events.clone();
   94        |window, cx| {
   95            let entity = cx.entity();
   96            cx.subscribe_in(
   97                &entity,
   98                window,
   99                move |_, _, event: &EditorEvent, _, _| match event {
  100                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  101                    EditorEvent::BufferEdited => {
  102                        events.borrow_mut().push(("editor1", "buffer edited"))
  103                    }
  104                    _ => {}
  105                },
  106            )
  107            .detach();
  108            Editor::for_buffer(buffer.clone(), None, window, cx)
  109        }
  110    });
  111
  112    let editor2 = cx.add_window({
  113        let events = events.clone();
  114        |window, cx| {
  115            cx.subscribe_in(
  116                &cx.entity(),
  117                window,
  118                move |_, _, event: &EditorEvent, _, _| match event {
  119                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  120                    EditorEvent::BufferEdited => {
  121                        events.borrow_mut().push(("editor2", "buffer edited"))
  122                    }
  123                    _ => {}
  124                },
  125            )
  126            .detach();
  127            Editor::for_buffer(buffer.clone(), None, window, cx)
  128        }
  129    });
  130
  131    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  132
  133    // Mutating editor 1 will emit an `Edited` event only for that editor.
  134    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  135    assert_eq!(
  136        mem::take(&mut *events.borrow_mut()),
  137        [
  138            ("editor1", "edited"),
  139            ("editor1", "buffer edited"),
  140            ("editor2", "buffer edited"),
  141        ]
  142    );
  143
  144    // Mutating editor 2 will emit an `Edited` event only for that editor.
  145    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  146    assert_eq!(
  147        mem::take(&mut *events.borrow_mut()),
  148        [
  149            ("editor2", "edited"),
  150            ("editor1", "buffer edited"),
  151            ("editor2", "buffer edited"),
  152        ]
  153    );
  154
  155    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  156    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  157    assert_eq!(
  158        mem::take(&mut *events.borrow_mut()),
  159        [
  160            ("editor1", "edited"),
  161            ("editor1", "buffer edited"),
  162            ("editor2", "buffer edited"),
  163        ]
  164    );
  165
  166    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  167    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  168    assert_eq!(
  169        mem::take(&mut *events.borrow_mut()),
  170        [
  171            ("editor1", "edited"),
  172            ("editor1", "buffer edited"),
  173            ("editor2", "buffer edited"),
  174        ]
  175    );
  176
  177    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  178    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  179    assert_eq!(
  180        mem::take(&mut *events.borrow_mut()),
  181        [
  182            ("editor2", "edited"),
  183            ("editor1", "buffer edited"),
  184            ("editor2", "buffer edited"),
  185        ]
  186    );
  187
  188    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  189    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  190    assert_eq!(
  191        mem::take(&mut *events.borrow_mut()),
  192        [
  193            ("editor2", "edited"),
  194            ("editor1", "buffer edited"),
  195            ("editor2", "buffer edited"),
  196        ]
  197    );
  198
  199    // No event is emitted when the mutation is a no-op.
  200    _ = editor2.update(cx, |editor, window, cx| {
  201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  202            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  203        });
  204
  205        editor.backspace(&Backspace, window, cx);
  206    });
  207    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  208}
  209
  210#[gpui::test]
  211fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  212    init_test(cx, |_| {});
  213
  214    let mut now = Instant::now();
  215    let group_interval = Duration::from_millis(1);
  216    let buffer = cx.new(|cx| {
  217        let mut buf = language::Buffer::local("123456", cx);
  218        buf.set_group_interval(group_interval);
  219        buf
  220    });
  221    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  222    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  223
  224    _ = editor.update(cx, |editor, window, cx| {
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  228        });
  229
  230        editor.insert("cd", window, cx);
  231        editor.end_transaction_at(now, cx);
  232        assert_eq!(editor.text(cx), "12cd56");
  233        assert_eq!(
  234            editor.selections.ranges(&editor.display_snapshot(cx)),
  235            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  236        );
  237
  238        editor.start_transaction_at(now, window, cx);
  239        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  240            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  241        });
  242        editor.insert("e", window, cx);
  243        editor.end_transaction_at(now, cx);
  244        assert_eq!(editor.text(cx), "12cde6");
  245        assert_eq!(
  246            editor.selections.ranges(&editor.display_snapshot(cx)),
  247            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  248        );
  249
  250        now += group_interval + Duration::from_millis(1);
  251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  252            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  253        });
  254
  255        // Simulate an edit in another editor
  256        buffer.update(cx, |buffer, cx| {
  257            buffer.start_transaction_at(now, cx);
  258            buffer.edit(
  259                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  260                None,
  261                cx,
  262            );
  263            buffer.edit(
  264                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  265                None,
  266                cx,
  267            );
  268            buffer.end_transaction_at(now, cx);
  269        });
  270
  271        assert_eq!(editor.text(cx), "ab2cde6");
  272        assert_eq!(
  273            editor.selections.ranges(&editor.display_snapshot(cx)),
  274            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  275        );
  276
  277        // Last transaction happened past the group interval in a different editor.
  278        // Undo it individually and don't restore selections.
  279        editor.undo(&Undo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  284        );
  285
  286        // First two transactions happened within the group interval in this editor.
  287        // Undo them together and restore selections.
  288        editor.undo(&Undo, window, cx);
  289        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  290        assert_eq!(editor.text(cx), "123456");
  291        assert_eq!(
  292            editor.selections.ranges(&editor.display_snapshot(cx)),
  293            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  294        );
  295
  296        // Redo the first two transactions together.
  297        editor.redo(&Redo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299        assert_eq!(
  300            editor.selections.ranges(&editor.display_snapshot(cx)),
  301            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  302        );
  303
  304        // Redo the last transaction on its own.
  305        editor.redo(&Redo, window, cx);
  306        assert_eq!(editor.text(cx), "ab2cde6");
  307        assert_eq!(
  308            editor.selections.ranges(&editor.display_snapshot(cx)),
  309            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  310        );
  311
  312        // Test empty transactions.
  313        editor.start_transaction_at(now, window, cx);
  314        editor.end_transaction_at(now, cx);
  315        editor.undo(&Undo, window, cx);
  316        assert_eq!(editor.text(cx), "12cde6");
  317    });
  318}
  319
  320#[gpui::test]
  321fn test_ime_composition(cx: &mut TestAppContext) {
  322    init_test(cx, |_| {});
  323
  324    let buffer = cx.new(|cx| {
  325        let mut buffer = language::Buffer::local("abcde", cx);
  326        // Ensure automatic grouping doesn't occur.
  327        buffer.set_group_interval(Duration::ZERO);
  328        buffer
  329    });
  330
  331    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  332    cx.add_window(|window, cx| {
  333        let mut editor = build_editor(buffer.clone(), window, cx);
  334
  335        // Start a new IME composition.
  336        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  337        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  339        assert_eq!(editor.text(cx), "äbcde");
  340        assert_eq!(
  341            editor.marked_text_ranges(cx),
  342            Some(vec![
  343                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  344            ])
  345        );
  346
  347        // Finalize IME composition.
  348        editor.replace_text_in_range(None, "ā", window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // IME composition edits are grouped and are undone/redone at once.
  353        editor.undo(&Default::default(), window, cx);
  354        assert_eq!(editor.text(cx), "abcde");
  355        assert_eq!(editor.marked_text_ranges(cx), None);
  356        editor.redo(&Default::default(), window, cx);
  357        assert_eq!(editor.text(cx), "ābcde");
  358        assert_eq!(editor.marked_text_ranges(cx), None);
  359
  360        // Start a new IME composition.
  361        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  362        assert_eq!(
  363            editor.marked_text_ranges(cx),
  364            Some(vec![
  365                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  366            ])
  367        );
  368
  369        // Undoing during an IME composition cancels it.
  370        editor.undo(&Default::default(), window, cx);
  371        assert_eq!(editor.text(cx), "ābcde");
  372        assert_eq!(editor.marked_text_ranges(cx), None);
  373
  374        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  375        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  376        assert_eq!(editor.text(cx), "ābcdè");
  377        assert_eq!(
  378            editor.marked_text_ranges(cx),
  379            Some(vec![
  380                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  381            ])
  382        );
  383
  384        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  385        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  386        assert_eq!(editor.text(cx), "ābcdę");
  387        assert_eq!(editor.marked_text_ranges(cx), None);
  388
  389        // Start a new IME composition with multiple cursors.
  390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  391            s.select_ranges([
  392                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  393                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  395            ])
  396        });
  397        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  398        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  399        assert_eq!(
  400            editor.marked_text_ranges(cx),
  401            Some(vec![
  402                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  403                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  405            ])
  406        );
  407
  408        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  409        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  410        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  411        assert_eq!(
  412            editor.marked_text_ranges(cx),
  413            Some(vec![
  414                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  415                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  417            ])
  418        );
  419
  420        // Finalize IME composition with multiple cursors.
  421        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  422        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  423        assert_eq!(editor.marked_text_ranges(cx), None);
  424
  425        editor
  426    });
  427}
  428
  429#[gpui::test]
  430fn test_selection_with_mouse(cx: &mut TestAppContext) {
  431    init_test(cx, |_| {});
  432
  433    let editor = cx.add_window(|window, cx| {
  434        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  435        build_editor(buffer, window, cx)
  436    });
  437
  438    _ = editor.update(cx, |editor, window, cx| {
  439        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  440    });
  441    assert_eq!(
  442        editor
  443            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  444            .unwrap(),
  445        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  446    );
  447
  448    _ = editor.update(cx, |editor, window, cx| {
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(3), 3),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  461            .unwrap(),
  462        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  463    );
  464
  465    _ = editor.update(cx, |editor, window, cx| {
  466        editor.update_selection(
  467            DisplayPoint::new(DisplayRow(1), 1),
  468            0,
  469            gpui::Point::<f32>::default(),
  470            window,
  471            cx,
  472        );
  473    });
  474
  475    assert_eq!(
  476        editor
  477            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  478            .unwrap(),
  479        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  480    );
  481
  482    _ = editor.update(cx, |editor, window, cx| {
  483        editor.end_selection(window, cx);
  484        editor.update_selection(
  485            DisplayPoint::new(DisplayRow(3), 3),
  486            0,
  487            gpui::Point::<f32>::default(),
  488            window,
  489            cx,
  490        );
  491    });
  492
  493    assert_eq!(
  494        editor
  495            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  496            .unwrap(),
  497        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  498    );
  499
  500    _ = editor.update(cx, |editor, window, cx| {
  501        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  502        editor.update_selection(
  503            DisplayPoint::new(DisplayRow(0), 0),
  504            0,
  505            gpui::Point::<f32>::default(),
  506            window,
  507            cx,
  508        );
  509    });
  510
  511    assert_eq!(
  512        editor
  513            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  514            .unwrap(),
  515        [
  516            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  517            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  518        ]
  519    );
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.end_selection(window, cx);
  523    });
  524
  525    assert_eq!(
  526        editor
  527            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  528            .unwrap(),
  529        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  530    );
  531}
  532
  533#[gpui::test]
  534fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  535    init_test(cx, |_| {});
  536
  537    let editor = cx.add_window(|window, cx| {
  538        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  539        build_editor(buffer, window, cx)
  540    });
  541
  542    _ = editor.update(cx, |editor, window, cx| {
  543        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  544    });
  545
  546    _ = editor.update(cx, |editor, window, cx| {
  547        editor.end_selection(window, cx);
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  552    });
  553
  554    _ = editor.update(cx, |editor, window, cx| {
  555        editor.end_selection(window, cx);
  556    });
  557
  558    assert_eq!(
  559        editor
  560            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  561            .unwrap(),
  562        [
  563            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  564            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  565        ]
  566    );
  567
  568    _ = editor.update(cx, |editor, window, cx| {
  569        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  570    });
  571
  572    _ = editor.update(cx, |editor, window, cx| {
  573        editor.end_selection(window, cx);
  574    });
  575
  576    assert_eq!(
  577        editor
  578            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  579            .unwrap(),
  580        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  581    );
  582}
  583
  584#[gpui::test]
  585fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  586    init_test(cx, |_| {});
  587
  588    let editor = cx.add_window(|window, cx| {
  589        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  590        build_editor(buffer, window, cx)
  591    });
  592
  593    _ = editor.update(cx, |editor, window, cx| {
  594        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  595        assert_eq!(
  596            display_ranges(editor, cx),
  597            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  598        );
  599    });
  600
  601    _ = editor.update(cx, |editor, window, cx| {
  602        editor.update_selection(
  603            DisplayPoint::new(DisplayRow(3), 3),
  604            0,
  605            gpui::Point::<f32>::default(),
  606            window,
  607            cx,
  608        );
  609        assert_eq!(
  610            display_ranges(editor, cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  612        );
  613    });
  614
  615    _ = editor.update(cx, |editor, window, cx| {
  616        editor.cancel(&Cancel, window, cx);
  617        editor.update_selection(
  618            DisplayPoint::new(DisplayRow(1), 1),
  619            0,
  620            gpui::Point::<f32>::default(),
  621            window,
  622            cx,
  623        );
  624        assert_eq!(
  625            display_ranges(editor, cx),
  626            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  627        );
  628    });
  629}
  630
  631#[gpui::test]
  632fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  633    init_test(cx, |_| {});
  634
  635    let editor = cx.add_window(|window, cx| {
  636        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  637        build_editor(buffer, window, cx)
  638    });
  639
  640    _ = editor.update(cx, |editor, window, cx| {
  641        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  642        assert_eq!(
  643            display_ranges(editor, cx),
  644            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  645        );
  646
  647        editor.move_down(&Default::default(), window, cx);
  648        assert_eq!(
  649            display_ranges(editor, cx),
  650            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  651        );
  652
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  654        assert_eq!(
  655            display_ranges(editor, cx),
  656            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  657        );
  658
  659        editor.move_up(&Default::default(), window, cx);
  660        assert_eq!(
  661            display_ranges(editor, cx),
  662            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  663        );
  664    });
  665}
  666
  667#[gpui::test]
  668fn test_extending_selection(cx: &mut TestAppContext) {
  669    init_test(cx, |_| {});
  670
  671    let editor = cx.add_window(|window, cx| {
  672        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  673        build_editor(buffer, window, cx)
  674    });
  675
  676    _ = editor.update(cx, |editor, window, cx| {
  677        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  678        editor.end_selection(window, cx);
  679        assert_eq!(
  680            display_ranges(editor, cx),
  681            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  682        );
  683
  684        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  685        editor.end_selection(window, cx);
  686        assert_eq!(
  687            display_ranges(editor, cx),
  688            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  689        );
  690
  691        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  692        editor.end_selection(window, cx);
  693        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  694        assert_eq!(
  695            display_ranges(editor, cx),
  696            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  697        );
  698
  699        editor.update_selection(
  700            DisplayPoint::new(DisplayRow(0), 1),
  701            0,
  702            gpui::Point::<f32>::default(),
  703            window,
  704            cx,
  705        );
  706        editor.end_selection(window, cx);
  707        assert_eq!(
  708            display_ranges(editor, cx),
  709            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  710        );
  711
  712        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  713        editor.end_selection(window, cx);
  714        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  715        editor.end_selection(window, cx);
  716        assert_eq!(
  717            display_ranges(editor, cx),
  718            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  719        );
  720
  721        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  722        assert_eq!(
  723            display_ranges(editor, cx),
  724            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  725        );
  726
  727        editor.update_selection(
  728            DisplayPoint::new(DisplayRow(0), 6),
  729            0,
  730            gpui::Point::<f32>::default(),
  731            window,
  732            cx,
  733        );
  734        assert_eq!(
  735            display_ranges(editor, cx),
  736            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  737        );
  738
  739        editor.update_selection(
  740            DisplayPoint::new(DisplayRow(0), 1),
  741            0,
  742            gpui::Point::<f32>::default(),
  743            window,
  744            cx,
  745        );
  746        editor.end_selection(window, cx);
  747        assert_eq!(
  748            display_ranges(editor, cx),
  749            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  750        );
  751    });
  752}
  753
  754#[gpui::test]
  755fn test_clone(cx: &mut TestAppContext) {
  756    init_test(cx, |_| {});
  757
  758    let (text, selection_ranges) = marked_text_ranges(
  759        indoc! {"
  760            one
  761            two
  762            threeˇ
  763            four
  764            fiveˇ
  765        "},
  766        true,
  767    );
  768
  769    let editor = cx.add_window(|window, cx| {
  770        let buffer = MultiBuffer::build_simple(&text, cx);
  771        build_editor(buffer, window, cx)
  772    });
  773
  774    _ = editor.update(cx, |editor, window, cx| {
  775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  776            s.select_ranges(
  777                selection_ranges
  778                    .iter()
  779                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  780            )
  781        });
  782        editor.fold_creases(
  783            vec![
  784                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  785                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  786            ],
  787            true,
  788            window,
  789            cx,
  790        );
  791    });
  792
  793    let cloned_editor = editor
  794        .update(cx, |editor, _, cx| {
  795            cx.open_window(Default::default(), |window, cx| {
  796                cx.new(|cx| editor.clone(window, cx))
  797            })
  798        })
  799        .unwrap()
  800        .unwrap();
  801
  802    let snapshot = editor
  803        .update(cx, |e, window, cx| e.snapshot(window, cx))
  804        .unwrap();
  805    let cloned_snapshot = cloned_editor
  806        .update(cx, |e, window, cx| e.snapshot(window, cx))
  807        .unwrap();
  808
  809    assert_eq!(
  810        cloned_editor
  811            .update(cx, |e, _, cx| e.display_text(cx))
  812            .unwrap(),
  813        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  814    );
  815    assert_eq!(
  816        cloned_snapshot
  817            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  818            .collect::<Vec<_>>(),
  819        snapshot
  820            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  821            .collect::<Vec<_>>(),
  822    );
  823    assert_set_eq!(
  824        cloned_editor
  825            .update(cx, |editor, _, cx| editor
  826                .selections
  827                .ranges::<Point>(&editor.display_snapshot(cx)))
  828            .unwrap(),
  829        editor
  830            .update(cx, |editor, _, cx| editor
  831                .selections
  832                .ranges(&editor.display_snapshot(cx)))
  833            .unwrap()
  834    );
  835    assert_set_eq!(
  836        cloned_editor
  837            .update(cx, |e, _window, cx| e
  838                .selections
  839                .display_ranges(&e.display_snapshot(cx)))
  840            .unwrap(),
  841        editor
  842            .update(cx, |e, _, cx| e
  843                .selections
  844                .display_ranges(&e.display_snapshot(cx)))
  845            .unwrap()
  846    );
  847}
  848
  849#[gpui::test]
  850async fn test_navigation_history(cx: &mut TestAppContext) {
  851    init_test(cx, |_| {});
  852
  853    use workspace::item::Item;
  854
  855    let fs = FakeFs::new(cx.executor());
  856    let project = Project::test(fs, [], cx).await;
  857    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  858    let pane = workspace
  859        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  860        .unwrap();
  861
  862    _ = workspace.update(cx, |_v, window, cx| {
  863        cx.new(|cx| {
  864            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  865            let mut editor = build_editor(buffer, window, cx);
  866            let handle = cx.entity();
  867            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  868
  869            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  870                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  871            }
  872
  873            // Move the cursor a small distance.
  874            // Nothing is added to the navigation history.
  875            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  876                s.select_display_ranges([
  877                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  878                ])
  879            });
  880            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  881                s.select_display_ranges([
  882                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  883                ])
  884            });
  885            assert!(pop_history(&mut editor, cx).is_none());
  886
  887            // Move the cursor a large distance.
  888            // The history can jump back to the previous position.
  889            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  890                s.select_display_ranges([
  891                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  892                ])
  893            });
  894            let nav_entry = pop_history(&mut editor, cx).unwrap();
  895            editor.navigate(nav_entry.data.unwrap(), window, cx);
  896            assert_eq!(nav_entry.item.id(), cx.entity_id());
  897            assert_eq!(
  898                editor
  899                    .selections
  900                    .display_ranges(&editor.display_snapshot(cx)),
  901                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  902            );
  903            assert!(pop_history(&mut editor, cx).is_none());
  904
  905            // Move the cursor a small distance via the mouse.
  906            // Nothing is added to the navigation history.
  907            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  908            editor.end_selection(window, cx);
  909            assert_eq!(
  910                editor
  911                    .selections
  912                    .display_ranges(&editor.display_snapshot(cx)),
  913                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  914            );
  915            assert!(pop_history(&mut editor, cx).is_none());
  916
  917            // Move the cursor a large distance via the mouse.
  918            // The history can jump back to the previous position.
  919            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  920            editor.end_selection(window, cx);
  921            assert_eq!(
  922                editor
  923                    .selections
  924                    .display_ranges(&editor.display_snapshot(cx)),
  925                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  926            );
  927            let nav_entry = pop_history(&mut editor, cx).unwrap();
  928            editor.navigate(nav_entry.data.unwrap(), window, cx);
  929            assert_eq!(nav_entry.item.id(), cx.entity_id());
  930            assert_eq!(
  931                editor
  932                    .selections
  933                    .display_ranges(&editor.display_snapshot(cx)),
  934                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  935            );
  936            assert!(pop_history(&mut editor, cx).is_none());
  937
  938            // Set scroll position to check later
  939            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  940            let original_scroll_position = editor.scroll_manager.anchor();
  941
  942            // Jump to the end of the document and adjust scroll
  943            editor.move_to_end(&MoveToEnd, window, cx);
  944            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  945            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  946
  947            let nav_entry = pop_history(&mut editor, cx).unwrap();
  948            editor.navigate(nav_entry.data.unwrap(), window, cx);
  949            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  950
  951            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  952            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  953            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  954            let invalid_point = Point::new(9999, 0);
  955            editor.navigate(
  956                Box::new(NavigationData {
  957                    cursor_anchor: invalid_anchor,
  958                    cursor_position: invalid_point,
  959                    scroll_anchor: ScrollAnchor {
  960                        anchor: invalid_anchor,
  961                        offset: Default::default(),
  962                    },
  963                    scroll_top_row: invalid_point.row,
  964                }),
  965                window,
  966                cx,
  967            );
  968            assert_eq!(
  969                editor
  970                    .selections
  971                    .display_ranges(&editor.display_snapshot(cx)),
  972                &[editor.max_point(cx)..editor.max_point(cx)]
  973            );
  974            assert_eq!(
  975                editor.scroll_position(cx),
  976                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  977            );
  978
  979            editor
  980        })
  981    });
  982}
  983
  984#[gpui::test]
  985fn test_cancel(cx: &mut TestAppContext) {
  986    init_test(cx, |_| {});
  987
  988    let editor = cx.add_window(|window, cx| {
  989        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  990        build_editor(buffer, window, cx)
  991    });
  992
  993    _ = editor.update(cx, |editor, window, cx| {
  994        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  995        editor.update_selection(
  996            DisplayPoint::new(DisplayRow(1), 1),
  997            0,
  998            gpui::Point::<f32>::default(),
  999            window,
 1000            cx,
 1001        );
 1002        editor.end_selection(window, cx);
 1003
 1004        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1005        editor.update_selection(
 1006            DisplayPoint::new(DisplayRow(0), 3),
 1007            0,
 1008            gpui::Point::<f32>::default(),
 1009            window,
 1010            cx,
 1011        );
 1012        editor.end_selection(window, cx);
 1013        assert_eq!(
 1014            display_ranges(editor, cx),
 1015            [
 1016                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1017                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1018            ]
 1019        );
 1020    });
 1021
 1022    _ = editor.update(cx, |editor, window, cx| {
 1023        editor.cancel(&Cancel, window, cx);
 1024        assert_eq!(
 1025            display_ranges(editor, cx),
 1026            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1027        );
 1028    });
 1029
 1030    _ = editor.update(cx, |editor, window, cx| {
 1031        editor.cancel(&Cancel, window, cx);
 1032        assert_eq!(
 1033            display_ranges(editor, cx),
 1034            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1035        );
 1036    });
 1037}
 1038
 1039#[gpui::test]
 1040fn test_fold_action(cx: &mut TestAppContext) {
 1041    init_test(cx, |_| {});
 1042
 1043    let editor = cx.add_window(|window, cx| {
 1044        let buffer = MultiBuffer::build_simple(
 1045            &"
 1046                impl Foo {
 1047                    // Hello!
 1048
 1049                    fn a() {
 1050                        1
 1051                    }
 1052
 1053                    fn b() {
 1054                        2
 1055                    }
 1056
 1057                    fn c() {
 1058                        3
 1059                    }
 1060                }
 1061            "
 1062            .unindent(),
 1063            cx,
 1064        );
 1065        build_editor(buffer, window, cx)
 1066    });
 1067
 1068    _ = editor.update(cx, |editor, window, cx| {
 1069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1070            s.select_display_ranges([
 1071                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1072            ]);
 1073        });
 1074        editor.fold(&Fold, window, cx);
 1075        assert_eq!(
 1076            editor.display_text(cx),
 1077            "
 1078                impl Foo {
 1079                    // Hello!
 1080
 1081                    fn a() {
 1082                        1
 1083                    }
 1084
 1085                    fn b() {⋯
 1086                    }
 1087
 1088                    fn c() {⋯
 1089                    }
 1090                }
 1091            "
 1092            .unindent(),
 1093        );
 1094
 1095        editor.fold(&Fold, window, cx);
 1096        assert_eq!(
 1097            editor.display_text(cx),
 1098            "
 1099                impl Foo {⋯
 1100                }
 1101            "
 1102            .unindent(),
 1103        );
 1104
 1105        editor.unfold_lines(&UnfoldLines, window, cx);
 1106        assert_eq!(
 1107            editor.display_text(cx),
 1108            "
 1109                impl Foo {
 1110                    // Hello!
 1111
 1112                    fn a() {
 1113                        1
 1114                    }
 1115
 1116                    fn b() {⋯
 1117                    }
 1118
 1119                    fn c() {⋯
 1120                    }
 1121                }
 1122            "
 1123            .unindent(),
 1124        );
 1125
 1126        editor.unfold_lines(&UnfoldLines, window, cx);
 1127        assert_eq!(
 1128            editor.display_text(cx),
 1129            editor.buffer.read(cx).read(cx).text()
 1130        );
 1131    });
 1132}
 1133
 1134#[gpui::test]
 1135fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1136    init_test(cx, |_| {});
 1137
 1138    let editor = cx.add_window(|window, cx| {
 1139        let buffer = MultiBuffer::build_simple(
 1140            &"
 1141                class Foo:
 1142                    # Hello!
 1143
 1144                    def a():
 1145                        print(1)
 1146
 1147                    def b():
 1148                        print(2)
 1149
 1150                    def c():
 1151                        print(3)
 1152            "
 1153            .unindent(),
 1154            cx,
 1155        );
 1156        build_editor(buffer, window, cx)
 1157    });
 1158
 1159    _ = editor.update(cx, |editor, window, cx| {
 1160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1161            s.select_display_ranges([
 1162                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1163            ]);
 1164        });
 1165        editor.fold(&Fold, window, cx);
 1166        assert_eq!(
 1167            editor.display_text(cx),
 1168            "
 1169                class Foo:
 1170                    # Hello!
 1171
 1172                    def a():
 1173                        print(1)
 1174
 1175                    def b():⋯
 1176
 1177                    def c():⋯
 1178            "
 1179            .unindent(),
 1180        );
 1181
 1182        editor.fold(&Fold, window, cx);
 1183        assert_eq!(
 1184            editor.display_text(cx),
 1185            "
 1186                class Foo:⋯
 1187            "
 1188            .unindent(),
 1189        );
 1190
 1191        editor.unfold_lines(&UnfoldLines, window, cx);
 1192        assert_eq!(
 1193            editor.display_text(cx),
 1194            "
 1195                class Foo:
 1196                    # Hello!
 1197
 1198                    def a():
 1199                        print(1)
 1200
 1201                    def b():⋯
 1202
 1203                    def c():⋯
 1204            "
 1205            .unindent(),
 1206        );
 1207
 1208        editor.unfold_lines(&UnfoldLines, window, cx);
 1209        assert_eq!(
 1210            editor.display_text(cx),
 1211            editor.buffer.read(cx).read(cx).text()
 1212        );
 1213    });
 1214}
 1215
 1216#[gpui::test]
 1217fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1218    init_test(cx, |_| {});
 1219
 1220    let editor = cx.add_window(|window, cx| {
 1221        let buffer = MultiBuffer::build_simple(
 1222            &"
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                    def c():
 1234                        print(3)
 1235
 1236
 1237            "
 1238            .unindent(),
 1239            cx,
 1240        );
 1241        build_editor(buffer, window, cx)
 1242    });
 1243
 1244    _ = editor.update(cx, |editor, window, cx| {
 1245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1246            s.select_display_ranges([
 1247                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1248            ]);
 1249        });
 1250        editor.fold(&Fold, window, cx);
 1251        assert_eq!(
 1252            editor.display_text(cx),
 1253            "
 1254                class Foo:
 1255                    # Hello!
 1256
 1257                    def a():
 1258                        print(1)
 1259
 1260                    def b():⋯
 1261
 1262
 1263                    def c():⋯
 1264
 1265
 1266            "
 1267            .unindent(),
 1268        );
 1269
 1270        editor.fold(&Fold, window, cx);
 1271        assert_eq!(
 1272            editor.display_text(cx),
 1273            "
 1274                class Foo:⋯
 1275
 1276
 1277            "
 1278            .unindent(),
 1279        );
 1280
 1281        editor.unfold_lines(&UnfoldLines, window, cx);
 1282        assert_eq!(
 1283            editor.display_text(cx),
 1284            "
 1285                class Foo:
 1286                    # Hello!
 1287
 1288                    def a():
 1289                        print(1)
 1290
 1291                    def b():⋯
 1292
 1293
 1294                    def c():⋯
 1295
 1296
 1297            "
 1298            .unindent(),
 1299        );
 1300
 1301        editor.unfold_lines(&UnfoldLines, window, cx);
 1302        assert_eq!(
 1303            editor.display_text(cx),
 1304            editor.buffer.read(cx).read(cx).text()
 1305        );
 1306    });
 1307}
 1308
 1309#[gpui::test]
 1310fn test_fold_at_level(cx: &mut TestAppContext) {
 1311    init_test(cx, |_| {});
 1312
 1313    let editor = cx.add_window(|window, cx| {
 1314        let buffer = MultiBuffer::build_simple(
 1315            &"
 1316                class Foo:
 1317                    # Hello!
 1318
 1319                    def a():
 1320                        print(1)
 1321
 1322                    def b():
 1323                        print(2)
 1324
 1325
 1326                class Bar:
 1327                    # World!
 1328
 1329                    def a():
 1330                        print(1)
 1331
 1332                    def b():
 1333                        print(2)
 1334
 1335
 1336            "
 1337            .unindent(),
 1338            cx,
 1339        );
 1340        build_editor(buffer, window, cx)
 1341    });
 1342
 1343    _ = editor.update(cx, |editor, window, cx| {
 1344        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1345        assert_eq!(
 1346            editor.display_text(cx),
 1347            "
 1348                class Foo:
 1349                    # Hello!
 1350
 1351                    def a():⋯
 1352
 1353                    def b():⋯
 1354
 1355
 1356                class Bar:
 1357                    # World!
 1358
 1359                    def a():⋯
 1360
 1361                    def b():⋯
 1362
 1363
 1364            "
 1365            .unindent(),
 1366        );
 1367
 1368        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1369        assert_eq!(
 1370            editor.display_text(cx),
 1371            "
 1372                class Foo:⋯
 1373
 1374
 1375                class Bar:⋯
 1376
 1377
 1378            "
 1379            .unindent(),
 1380        );
 1381
 1382        editor.unfold_all(&UnfoldAll, window, cx);
 1383        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1384        assert_eq!(
 1385            editor.display_text(cx),
 1386            "
 1387                class Foo:
 1388                    # Hello!
 1389
 1390                    def a():
 1391                        print(1)
 1392
 1393                    def b():
 1394                        print(2)
 1395
 1396
 1397                class Bar:
 1398                    # World!
 1399
 1400                    def a():
 1401                        print(1)
 1402
 1403                    def b():
 1404                        print(2)
 1405
 1406
 1407            "
 1408            .unindent(),
 1409        );
 1410
 1411        assert_eq!(
 1412            editor.display_text(cx),
 1413            editor.buffer.read(cx).read(cx).text()
 1414        );
 1415        let (_, positions) = marked_text_ranges(
 1416            &"
 1417                       class Foo:
 1418                           # Hello!
 1419
 1420                           def a():
 1421                              print(1)
 1422
 1423                           def b():
 1424                               p«riˇ»nt(2)
 1425
 1426
 1427                       class Bar:
 1428                           # World!
 1429
 1430                           def a():
 1431                               «ˇprint(1)
 1432
 1433                           def b():
 1434                               print(2)»
 1435
 1436
 1437                   "
 1438            .unindent(),
 1439            true,
 1440        );
 1441
 1442        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1443            s.select_ranges(
 1444                positions
 1445                    .iter()
 1446                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1447            )
 1448        });
 1449
 1450        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1451        assert_eq!(
 1452            editor.display_text(cx),
 1453            "
 1454                class Foo:
 1455                    # Hello!
 1456
 1457                    def a():⋯
 1458
 1459                    def b():
 1460                        print(2)
 1461
 1462
 1463                class Bar:
 1464                    # World!
 1465
 1466                    def a():
 1467                        print(1)
 1468
 1469                    def b():
 1470                        print(2)
 1471
 1472
 1473            "
 1474            .unindent(),
 1475        );
 1476    });
 1477}
 1478
 1479#[gpui::test]
 1480fn test_move_cursor(cx: &mut TestAppContext) {
 1481    init_test(cx, |_| {});
 1482
 1483    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1484    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1485
 1486    buffer.update(cx, |buffer, cx| {
 1487        buffer.edit(
 1488            vec![
 1489                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1490                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1491            ],
 1492            None,
 1493            cx,
 1494        );
 1495    });
 1496    _ = editor.update(cx, |editor, window, cx| {
 1497        assert_eq!(
 1498            display_ranges(editor, cx),
 1499            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1500        );
 1501
 1502        editor.move_down(&MoveDown, window, cx);
 1503        assert_eq!(
 1504            display_ranges(editor, cx),
 1505            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1506        );
 1507
 1508        editor.move_right(&MoveRight, window, cx);
 1509        assert_eq!(
 1510            display_ranges(editor, cx),
 1511            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1512        );
 1513
 1514        editor.move_left(&MoveLeft, window, cx);
 1515        assert_eq!(
 1516            display_ranges(editor, cx),
 1517            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1518        );
 1519
 1520        editor.move_up(&MoveUp, window, cx);
 1521        assert_eq!(
 1522            display_ranges(editor, cx),
 1523            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1524        );
 1525
 1526        editor.move_to_end(&MoveToEnd, window, cx);
 1527        assert_eq!(
 1528            display_ranges(editor, cx),
 1529            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1530        );
 1531
 1532        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1533        assert_eq!(
 1534            display_ranges(editor, cx),
 1535            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1536        );
 1537
 1538        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1539            s.select_display_ranges([
 1540                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1541            ]);
 1542        });
 1543        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1544        assert_eq!(
 1545            display_ranges(editor, cx),
 1546            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1547        );
 1548
 1549        editor.select_to_end(&SelectToEnd, window, cx);
 1550        assert_eq!(
 1551            display_ranges(editor, cx),
 1552            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1553        );
 1554    });
 1555}
 1556
 1557#[gpui::test]
 1558fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1559    init_test(cx, |_| {});
 1560
 1561    let editor = cx.add_window(|window, cx| {
 1562        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1563        build_editor(buffer, window, cx)
 1564    });
 1565
 1566    assert_eq!('🟥'.len_utf8(), 4);
 1567    assert_eq!('α'.len_utf8(), 2);
 1568
 1569    _ = editor.update(cx, |editor, window, cx| {
 1570        editor.fold_creases(
 1571            vec![
 1572                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1573                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1574                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1575            ],
 1576            true,
 1577            window,
 1578            cx,
 1579        );
 1580        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1581
 1582        editor.move_right(&MoveRight, window, cx);
 1583        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1584        editor.move_right(&MoveRight, window, cx);
 1585        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1586        editor.move_right(&MoveRight, window, cx);
 1587        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1588
 1589        editor.move_down(&MoveDown, window, cx);
 1590        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1591        editor.move_left(&MoveLeft, window, cx);
 1592        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1593        editor.move_left(&MoveLeft, window, cx);
 1594        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1595        editor.move_left(&MoveLeft, window, cx);
 1596        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1597
 1598        editor.move_down(&MoveDown, window, cx);
 1599        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1600        editor.move_right(&MoveRight, window, cx);
 1601        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1602        editor.move_right(&MoveRight, window, cx);
 1603        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1604        editor.move_right(&MoveRight, window, cx);
 1605        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1606
 1607        editor.move_up(&MoveUp, window, cx);
 1608        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1609        editor.move_down(&MoveDown, window, cx);
 1610        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1611        editor.move_up(&MoveUp, window, cx);
 1612        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1613
 1614        editor.move_up(&MoveUp, window, cx);
 1615        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1616        editor.move_left(&MoveLeft, window, cx);
 1617        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1618        editor.move_left(&MoveLeft, window, cx);
 1619        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1620    });
 1621}
 1622
 1623#[gpui::test]
 1624fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1625    init_test(cx, |_| {});
 1626
 1627    let editor = cx.add_window(|window, cx| {
 1628        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1629        build_editor(buffer, window, cx)
 1630    });
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1633            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1634        });
 1635
 1636        // moving above start of document should move selection to start of document,
 1637        // but the next move down should still be at the original goal_x
 1638        editor.move_up(&MoveUp, window, cx);
 1639        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1640
 1641        editor.move_down(&MoveDown, window, cx);
 1642        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1643
 1644        editor.move_down(&MoveDown, window, cx);
 1645        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1646
 1647        editor.move_down(&MoveDown, window, cx);
 1648        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1649
 1650        editor.move_down(&MoveDown, window, cx);
 1651        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1652
 1653        // moving past end of document should not change goal_x
 1654        editor.move_down(&MoveDown, window, cx);
 1655        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1656
 1657        editor.move_down(&MoveDown, window, cx);
 1658        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1659
 1660        editor.move_up(&MoveUp, window, cx);
 1661        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1662
 1663        editor.move_up(&MoveUp, window, cx);
 1664        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1665
 1666        editor.move_up(&MoveUp, window, cx);
 1667        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1668    });
 1669}
 1670
 1671#[gpui::test]
 1672fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1673    init_test(cx, |_| {});
 1674    let move_to_beg = MoveToBeginningOfLine {
 1675        stop_at_soft_wraps: true,
 1676        stop_at_indent: true,
 1677    };
 1678
 1679    let delete_to_beg = DeleteToBeginningOfLine {
 1680        stop_at_indent: false,
 1681    };
 1682
 1683    let move_to_end = MoveToEndOfLine {
 1684        stop_at_soft_wraps: true,
 1685    };
 1686
 1687    let editor = cx.add_window(|window, cx| {
 1688        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1689        build_editor(buffer, window, cx)
 1690    });
 1691    _ = editor.update(cx, |editor, window, cx| {
 1692        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1693            s.select_display_ranges([
 1694                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1695                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1696            ]);
 1697        });
 1698    });
 1699
 1700    _ = editor.update(cx, |editor, window, cx| {
 1701        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1702        assert_eq!(
 1703            display_ranges(editor, cx),
 1704            &[
 1705                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1706                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1707            ]
 1708        );
 1709    });
 1710
 1711    _ = editor.update(cx, |editor, window, cx| {
 1712        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1713        assert_eq!(
 1714            display_ranges(editor, cx),
 1715            &[
 1716                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1717                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1718            ]
 1719        );
 1720    });
 1721
 1722    _ = editor.update(cx, |editor, window, cx| {
 1723        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1724        assert_eq!(
 1725            display_ranges(editor, cx),
 1726            &[
 1727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1728                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1729            ]
 1730        );
 1731    });
 1732
 1733    _ = editor.update(cx, |editor, window, cx| {
 1734        editor.move_to_end_of_line(&move_to_end, window, cx);
 1735        assert_eq!(
 1736            display_ranges(editor, cx),
 1737            &[
 1738                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1739                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1740            ]
 1741        );
 1742    });
 1743
 1744    // Moving to the end of line again is a no-op.
 1745    _ = editor.update(cx, |editor, window, cx| {
 1746        editor.move_to_end_of_line(&move_to_end, window, cx);
 1747        assert_eq!(
 1748            display_ranges(editor, cx),
 1749            &[
 1750                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1751                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1752            ]
 1753        );
 1754    });
 1755
 1756    _ = editor.update(cx, |editor, window, cx| {
 1757        editor.move_left(&MoveLeft, window, cx);
 1758        editor.select_to_beginning_of_line(
 1759            &SelectToBeginningOfLine {
 1760                stop_at_soft_wraps: true,
 1761                stop_at_indent: true,
 1762            },
 1763            window,
 1764            cx,
 1765        );
 1766        assert_eq!(
 1767            display_ranges(editor, cx),
 1768            &[
 1769                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1770                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1771            ]
 1772        );
 1773    });
 1774
 1775    _ = editor.update(cx, |editor, window, cx| {
 1776        editor.select_to_beginning_of_line(
 1777            &SelectToBeginningOfLine {
 1778                stop_at_soft_wraps: true,
 1779                stop_at_indent: true,
 1780            },
 1781            window,
 1782            cx,
 1783        );
 1784        assert_eq!(
 1785            display_ranges(editor, cx),
 1786            &[
 1787                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1788                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1789            ]
 1790        );
 1791    });
 1792
 1793    _ = editor.update(cx, |editor, window, cx| {
 1794        editor.select_to_beginning_of_line(
 1795            &SelectToBeginningOfLine {
 1796                stop_at_soft_wraps: true,
 1797                stop_at_indent: true,
 1798            },
 1799            window,
 1800            cx,
 1801        );
 1802        assert_eq!(
 1803            display_ranges(editor, cx),
 1804            &[
 1805                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1806                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1807            ]
 1808        );
 1809    });
 1810
 1811    _ = editor.update(cx, |editor, window, cx| {
 1812        editor.select_to_end_of_line(
 1813            &SelectToEndOfLine {
 1814                stop_at_soft_wraps: true,
 1815            },
 1816            window,
 1817            cx,
 1818        );
 1819        assert_eq!(
 1820            display_ranges(editor, cx),
 1821            &[
 1822                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1823                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1824            ]
 1825        );
 1826    });
 1827
 1828    _ = editor.update(cx, |editor, window, cx| {
 1829        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1830        assert_eq!(editor.display_text(cx), "ab\n  de");
 1831        assert_eq!(
 1832            display_ranges(editor, cx),
 1833            &[
 1834                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1835                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1836            ]
 1837        );
 1838    });
 1839
 1840    _ = editor.update(cx, |editor, window, cx| {
 1841        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1842        assert_eq!(editor.display_text(cx), "\n");
 1843        assert_eq!(
 1844            display_ranges(editor, cx),
 1845            &[
 1846                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1847                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1848            ]
 1849        );
 1850    });
 1851}
 1852
 1853#[gpui::test]
 1854fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1855    init_test(cx, |_| {});
 1856    let move_to_beg = MoveToBeginningOfLine {
 1857        stop_at_soft_wraps: false,
 1858        stop_at_indent: false,
 1859    };
 1860
 1861    let move_to_end = MoveToEndOfLine {
 1862        stop_at_soft_wraps: false,
 1863    };
 1864
 1865    let editor = cx.add_window(|window, cx| {
 1866        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1867        build_editor(buffer, window, cx)
 1868    });
 1869
 1870    _ = editor.update(cx, |editor, window, cx| {
 1871        editor.set_wrap_width(Some(140.0.into()), cx);
 1872
 1873        // We expect the following lines after wrapping
 1874        // ```
 1875        // thequickbrownfox
 1876        // jumpedoverthelazydo
 1877        // gs
 1878        // ```
 1879        // The final `gs` was soft-wrapped onto a new line.
 1880        assert_eq!(
 1881            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1882            editor.display_text(cx),
 1883        );
 1884
 1885        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1886        // Start the cursor at the `k` on the first line
 1887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1888            s.select_display_ranges([
 1889                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1890            ]);
 1891        });
 1892
 1893        // Moving to the beginning of the line should put us at the beginning of the line.
 1894        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1895        assert_eq!(
 1896            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1897            display_ranges(editor, cx)
 1898        );
 1899
 1900        // Moving to the end of the line should put us at the end of the line.
 1901        editor.move_to_end_of_line(&move_to_end, window, cx);
 1902        assert_eq!(
 1903            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1904            display_ranges(editor, cx)
 1905        );
 1906
 1907        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1908        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1910            s.select_display_ranges([
 1911                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1912            ]);
 1913        });
 1914
 1915        // Moving to the beginning of the line should put us at the start of the second line of
 1916        // display text, i.e., the `j`.
 1917        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1918        assert_eq!(
 1919            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1920            display_ranges(editor, cx)
 1921        );
 1922
 1923        // Moving to the beginning of the line again should be a no-op.
 1924        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1925        assert_eq!(
 1926            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1927            display_ranges(editor, cx)
 1928        );
 1929
 1930        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1931        // next display line.
 1932        editor.move_to_end_of_line(&move_to_end, window, cx);
 1933        assert_eq!(
 1934            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1935            display_ranges(editor, cx)
 1936        );
 1937
 1938        // Moving to the end of the line again should be a no-op.
 1939        editor.move_to_end_of_line(&move_to_end, window, cx);
 1940        assert_eq!(
 1941            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1942            display_ranges(editor, cx)
 1943        );
 1944    });
 1945}
 1946
 1947#[gpui::test]
 1948fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1949    init_test(cx, |_| {});
 1950
 1951    let move_to_beg = MoveToBeginningOfLine {
 1952        stop_at_soft_wraps: true,
 1953        stop_at_indent: true,
 1954    };
 1955
 1956    let select_to_beg = SelectToBeginningOfLine {
 1957        stop_at_soft_wraps: true,
 1958        stop_at_indent: true,
 1959    };
 1960
 1961    let delete_to_beg = DeleteToBeginningOfLine {
 1962        stop_at_indent: true,
 1963    };
 1964
 1965    let move_to_end = MoveToEndOfLine {
 1966        stop_at_soft_wraps: false,
 1967    };
 1968
 1969    let editor = cx.add_window(|window, cx| {
 1970        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1971        build_editor(buffer, window, cx)
 1972    });
 1973
 1974    _ = editor.update(cx, |editor, window, cx| {
 1975        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1976            s.select_display_ranges([
 1977                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1978                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1979            ]);
 1980        });
 1981
 1982        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1983        // and the second cursor at the first non-whitespace character in the line.
 1984        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1985        assert_eq!(
 1986            display_ranges(editor, cx),
 1987            &[
 1988                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1989                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1990            ]
 1991        );
 1992
 1993        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1994        // and should move the second cursor to the beginning of the line.
 1995        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1996        assert_eq!(
 1997            display_ranges(editor, cx),
 1998            &[
 1999                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2000                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2001            ]
 2002        );
 2003
 2004        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2005        // and should move the second cursor back to the first non-whitespace character in the line.
 2006        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2007        assert_eq!(
 2008            display_ranges(editor, cx),
 2009            &[
 2010                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2011                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2012            ]
 2013        );
 2014
 2015        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2016        // and to the first non-whitespace character in the line for the second cursor.
 2017        editor.move_to_end_of_line(&move_to_end, window, cx);
 2018        editor.move_left(&MoveLeft, window, cx);
 2019        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2020        assert_eq!(
 2021            display_ranges(editor, cx),
 2022            &[
 2023                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2024                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2025            ]
 2026        );
 2027
 2028        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2029        // and should select to the beginning of the line for the second cursor.
 2030        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2031        assert_eq!(
 2032            display_ranges(editor, cx),
 2033            &[
 2034                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2035                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2036            ]
 2037        );
 2038
 2039        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2040        // and should delete to the first non-whitespace character in the line for the second cursor.
 2041        editor.move_to_end_of_line(&move_to_end, window, cx);
 2042        editor.move_left(&MoveLeft, window, cx);
 2043        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2044        assert_eq!(editor.text(cx), "c\n  f");
 2045    });
 2046}
 2047
 2048#[gpui::test]
 2049fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2050    init_test(cx, |_| {});
 2051
 2052    let move_to_beg = MoveToBeginningOfLine {
 2053        stop_at_soft_wraps: true,
 2054        stop_at_indent: true,
 2055    };
 2056
 2057    let editor = cx.add_window(|window, cx| {
 2058        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2059        build_editor(buffer, window, cx)
 2060    });
 2061
 2062    _ = editor.update(cx, |editor, window, cx| {
 2063        // test cursor between line_start and indent_start
 2064        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2065            s.select_display_ranges([
 2066                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2067            ]);
 2068        });
 2069
 2070        // cursor should move to line_start
 2071        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2072        assert_eq!(
 2073            display_ranges(editor, cx),
 2074            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2075        );
 2076
 2077        // cursor should move to indent_start
 2078        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2079        assert_eq!(
 2080            display_ranges(editor, cx),
 2081            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2082        );
 2083
 2084        // cursor should move to back to line_start
 2085        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2086        assert_eq!(
 2087            display_ranges(editor, cx),
 2088            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2089        );
 2090    });
 2091}
 2092
 2093#[gpui::test]
 2094fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2095    init_test(cx, |_| {});
 2096
 2097    let editor = cx.add_window(|window, cx| {
 2098        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2099        build_editor(buffer, window, cx)
 2100    });
 2101    _ = editor.update(cx, |editor, window, cx| {
 2102        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2103            s.select_display_ranges([
 2104                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2105                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2106            ])
 2107        });
 2108        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2109        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2110
 2111        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2112        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2113
 2114        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2115        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2116
 2117        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2118        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2119
 2120        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2121        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2122
 2123        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2124        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2125
 2126        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2127        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2128
 2129        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2130        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2131
 2132        editor.move_right(&MoveRight, window, cx);
 2133        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2134        assert_selection_ranges(
 2135            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2136            editor,
 2137            cx,
 2138        );
 2139
 2140        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2141        assert_selection_ranges(
 2142            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2143            editor,
 2144            cx,
 2145        );
 2146
 2147        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2148        assert_selection_ranges(
 2149            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2150            editor,
 2151            cx,
 2152        );
 2153    });
 2154}
 2155
 2156#[gpui::test]
 2157fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2158    init_test(cx, |_| {});
 2159
 2160    let editor = cx.add_window(|window, cx| {
 2161        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2162        build_editor(buffer, window, cx)
 2163    });
 2164
 2165    _ = editor.update(cx, |editor, window, cx| {
 2166        editor.set_wrap_width(Some(140.0.into()), cx);
 2167        assert_eq!(
 2168            editor.display_text(cx),
 2169            "use one::{\n    two::three::\n    four::five\n};"
 2170        );
 2171
 2172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2173            s.select_display_ranges([
 2174                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2175            ]);
 2176        });
 2177
 2178        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2179        assert_eq!(
 2180            display_ranges(editor, cx),
 2181            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2182        );
 2183
 2184        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2185        assert_eq!(
 2186            display_ranges(editor, cx),
 2187            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2188        );
 2189
 2190        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2191        assert_eq!(
 2192            display_ranges(editor, cx),
 2193            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2194        );
 2195
 2196        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2197        assert_eq!(
 2198            display_ranges(editor, cx),
 2199            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2200        );
 2201
 2202        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2203        assert_eq!(
 2204            display_ranges(editor, cx),
 2205            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2206        );
 2207
 2208        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2209        assert_eq!(
 2210            display_ranges(editor, cx),
 2211            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2212        );
 2213    });
 2214}
 2215
 2216#[gpui::test]
 2217async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2218    init_test(cx, |_| {});
 2219    let mut cx = EditorTestContext::new(cx).await;
 2220
 2221    let line_height = cx.editor(|editor, window, _| {
 2222        editor
 2223            .style()
 2224            .unwrap()
 2225            .text
 2226            .line_height_in_pixels(window.rem_size())
 2227    });
 2228    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2229
 2230    cx.set_state(
 2231        &r#"ˇone
 2232        two
 2233
 2234        three
 2235        fourˇ
 2236        five
 2237
 2238        six"#
 2239            .unindent(),
 2240    );
 2241
 2242    cx.update_editor(|editor, window, cx| {
 2243        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2244    });
 2245    cx.assert_editor_state(
 2246        &r#"one
 2247        two
 2248        ˇ
 2249        three
 2250        four
 2251        five
 2252        ˇ
 2253        six"#
 2254            .unindent(),
 2255    );
 2256
 2257    cx.update_editor(|editor, window, cx| {
 2258        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2259    });
 2260    cx.assert_editor_state(
 2261        &r#"one
 2262        two
 2263
 2264        three
 2265        four
 2266        five
 2267        ˇ
 2268        sixˇ"#
 2269            .unindent(),
 2270    );
 2271
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2274    });
 2275    cx.assert_editor_state(
 2276        &r#"one
 2277        two
 2278
 2279        three
 2280        four
 2281        five
 2282
 2283        sixˇ"#
 2284            .unindent(),
 2285    );
 2286
 2287    cx.update_editor(|editor, window, cx| {
 2288        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2289    });
 2290    cx.assert_editor_state(
 2291        &r#"one
 2292        two
 2293
 2294        three
 2295        four
 2296        five
 2297        ˇ
 2298        six"#
 2299            .unindent(),
 2300    );
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(
 2306        &r#"one
 2307        two
 2308        ˇ
 2309        three
 2310        four
 2311        five
 2312
 2313        six"#
 2314            .unindent(),
 2315    );
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(
 2321        &r#"ˇone
 2322        two
 2323
 2324        three
 2325        four
 2326        five
 2327
 2328        six"#
 2329            .unindent(),
 2330    );
 2331}
 2332
 2333#[gpui::test]
 2334async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2335    init_test(cx, |_| {});
 2336    let mut cx = EditorTestContext::new(cx).await;
 2337    let line_height = cx.editor(|editor, window, _| {
 2338        editor
 2339            .style()
 2340            .unwrap()
 2341            .text
 2342            .line_height_in_pixels(window.rem_size())
 2343    });
 2344    let window = cx.window;
 2345    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2346
 2347    cx.set_state(
 2348        r#"ˇone
 2349        two
 2350        three
 2351        four
 2352        five
 2353        six
 2354        seven
 2355        eight
 2356        nine
 2357        ten
 2358        "#,
 2359    );
 2360
 2361    cx.update_editor(|editor, window, cx| {
 2362        assert_eq!(
 2363            editor.snapshot(window, cx).scroll_position(),
 2364            gpui::Point::new(0., 0.)
 2365        );
 2366        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2367        assert_eq!(
 2368            editor.snapshot(window, cx).scroll_position(),
 2369            gpui::Point::new(0., 3.)
 2370        );
 2371        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2372        assert_eq!(
 2373            editor.snapshot(window, cx).scroll_position(),
 2374            gpui::Point::new(0., 6.)
 2375        );
 2376        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2377        assert_eq!(
 2378            editor.snapshot(window, cx).scroll_position(),
 2379            gpui::Point::new(0., 3.)
 2380        );
 2381
 2382        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2383        assert_eq!(
 2384            editor.snapshot(window, cx).scroll_position(),
 2385            gpui::Point::new(0., 1.)
 2386        );
 2387        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2388        assert_eq!(
 2389            editor.snapshot(window, cx).scroll_position(),
 2390            gpui::Point::new(0., 3.)
 2391        );
 2392    });
 2393}
 2394
 2395#[gpui::test]
 2396async fn test_autoscroll(cx: &mut TestAppContext) {
 2397    init_test(cx, |_| {});
 2398    let mut cx = EditorTestContext::new(cx).await;
 2399
 2400    let line_height = cx.update_editor(|editor, window, cx| {
 2401        editor.set_vertical_scroll_margin(2, cx);
 2402        editor
 2403            .style()
 2404            .unwrap()
 2405            .text
 2406            .line_height_in_pixels(window.rem_size())
 2407    });
 2408    let window = cx.window;
 2409    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2410
 2411    cx.set_state(
 2412        r#"ˇone
 2413            two
 2414            three
 2415            four
 2416            five
 2417            six
 2418            seven
 2419            eight
 2420            nine
 2421            ten
 2422        "#,
 2423    );
 2424    cx.update_editor(|editor, window, cx| {
 2425        assert_eq!(
 2426            editor.snapshot(window, cx).scroll_position(),
 2427            gpui::Point::new(0., 0.0)
 2428        );
 2429    });
 2430
 2431    // Add a cursor below the visible area. Since both cursors cannot fit
 2432    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2433    // allows the vertical scroll margin below that cursor.
 2434    cx.update_editor(|editor, window, cx| {
 2435        editor.change_selections(Default::default(), window, cx, |selections| {
 2436            selections.select_ranges([
 2437                Point::new(0, 0)..Point::new(0, 0),
 2438                Point::new(6, 0)..Point::new(6, 0),
 2439            ]);
 2440        })
 2441    });
 2442    cx.update_editor(|editor, window, cx| {
 2443        assert_eq!(
 2444            editor.snapshot(window, cx).scroll_position(),
 2445            gpui::Point::new(0., 3.0)
 2446        );
 2447    });
 2448
 2449    // Move down. The editor cursor scrolls down to track the newest cursor.
 2450    cx.update_editor(|editor, window, cx| {
 2451        editor.move_down(&Default::default(), window, cx);
 2452    });
 2453    cx.update_editor(|editor, window, cx| {
 2454        assert_eq!(
 2455            editor.snapshot(window, cx).scroll_position(),
 2456            gpui::Point::new(0., 4.0)
 2457        );
 2458    });
 2459
 2460    // Add a cursor above the visible area. Since both cursors fit on screen,
 2461    // the editor scrolls to show both.
 2462    cx.update_editor(|editor, window, cx| {
 2463        editor.change_selections(Default::default(), window, cx, |selections| {
 2464            selections.select_ranges([
 2465                Point::new(1, 0)..Point::new(1, 0),
 2466                Point::new(6, 0)..Point::new(6, 0),
 2467            ]);
 2468        })
 2469    });
 2470    cx.update_editor(|editor, window, cx| {
 2471        assert_eq!(
 2472            editor.snapshot(window, cx).scroll_position(),
 2473            gpui::Point::new(0., 1.0)
 2474        );
 2475    });
 2476}
 2477
 2478#[gpui::test]
 2479async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481    let mut cx = EditorTestContext::new(cx).await;
 2482
 2483    let line_height = cx.editor(|editor, window, _cx| {
 2484        editor
 2485            .style()
 2486            .unwrap()
 2487            .text
 2488            .line_height_in_pixels(window.rem_size())
 2489    });
 2490    let window = cx.window;
 2491    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2492    cx.set_state(
 2493        &r#"
 2494        ˇone
 2495        two
 2496        threeˇ
 2497        four
 2498        five
 2499        six
 2500        seven
 2501        eight
 2502        nine
 2503        ten
 2504        "#
 2505        .unindent(),
 2506    );
 2507
 2508    cx.update_editor(|editor, window, cx| {
 2509        editor.move_page_down(&MovePageDown::default(), window, cx)
 2510    });
 2511    cx.assert_editor_state(
 2512        &r#"
 2513        one
 2514        two
 2515        three
 2516        ˇfour
 2517        five
 2518        sixˇ
 2519        seven
 2520        eight
 2521        nine
 2522        ten
 2523        "#
 2524        .unindent(),
 2525    );
 2526
 2527    cx.update_editor(|editor, window, cx| {
 2528        editor.move_page_down(&MovePageDown::default(), window, cx)
 2529    });
 2530    cx.assert_editor_state(
 2531        &r#"
 2532        one
 2533        two
 2534        three
 2535        four
 2536        five
 2537        six
 2538        ˇseven
 2539        eight
 2540        nineˇ
 2541        ten
 2542        "#
 2543        .unindent(),
 2544    );
 2545
 2546    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2547    cx.assert_editor_state(
 2548        &r#"
 2549        one
 2550        two
 2551        three
 2552        ˇfour
 2553        five
 2554        sixˇ
 2555        seven
 2556        eight
 2557        nine
 2558        ten
 2559        "#
 2560        .unindent(),
 2561    );
 2562
 2563    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2564    cx.assert_editor_state(
 2565        &r#"
 2566        ˇone
 2567        two
 2568        threeˇ
 2569        four
 2570        five
 2571        six
 2572        seven
 2573        eight
 2574        nine
 2575        ten
 2576        "#
 2577        .unindent(),
 2578    );
 2579
 2580    // Test select collapsing
 2581    cx.update_editor(|editor, window, cx| {
 2582        editor.move_page_down(&MovePageDown::default(), window, cx);
 2583        editor.move_page_down(&MovePageDown::default(), window, cx);
 2584        editor.move_page_down(&MovePageDown::default(), window, cx);
 2585    });
 2586    cx.assert_editor_state(
 2587        &r#"
 2588        one
 2589        two
 2590        three
 2591        four
 2592        five
 2593        six
 2594        seven
 2595        eight
 2596        nine
 2597        ˇten
 2598        ˇ"#
 2599        .unindent(),
 2600    );
 2601}
 2602
 2603#[gpui::test]
 2604async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2605    init_test(cx, |_| {});
 2606    let mut cx = EditorTestContext::new(cx).await;
 2607    cx.set_state("one «two threeˇ» four");
 2608    cx.update_editor(|editor, window, cx| {
 2609        editor.delete_to_beginning_of_line(
 2610            &DeleteToBeginningOfLine {
 2611                stop_at_indent: false,
 2612            },
 2613            window,
 2614            cx,
 2615        );
 2616        assert_eq!(editor.text(cx), " four");
 2617    });
 2618}
 2619
 2620#[gpui::test]
 2621async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2622    init_test(cx, |_| {});
 2623
 2624    let mut cx = EditorTestContext::new(cx).await;
 2625
 2626    // For an empty selection, the preceding word fragment is deleted.
 2627    // For non-empty selections, only selected characters are deleted.
 2628    cx.set_state("onˇe two t«hreˇ»e four");
 2629    cx.update_editor(|editor, window, cx| {
 2630        editor.delete_to_previous_word_start(
 2631            &DeleteToPreviousWordStart {
 2632                ignore_newlines: false,
 2633                ignore_brackets: false,
 2634            },
 2635            window,
 2636            cx,
 2637        );
 2638    });
 2639    cx.assert_editor_state("ˇe two tˇe four");
 2640
 2641    cx.set_state("e tˇwo te «fˇ»our");
 2642    cx.update_editor(|editor, window, cx| {
 2643        editor.delete_to_next_word_end(
 2644            &DeleteToNextWordEnd {
 2645                ignore_newlines: false,
 2646                ignore_brackets: false,
 2647            },
 2648            window,
 2649            cx,
 2650        );
 2651    });
 2652    cx.assert_editor_state("e tˇ te ˇour");
 2653}
 2654
 2655#[gpui::test]
 2656async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2657    init_test(cx, |_| {});
 2658
 2659    let mut cx = EditorTestContext::new(cx).await;
 2660
 2661    cx.set_state("here is some text    ˇwith a space");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_previous_word_start(
 2664            &DeleteToPreviousWordStart {
 2665                ignore_newlines: false,
 2666                ignore_brackets: true,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2673    cx.assert_editor_state("here is some textˇwith a space");
 2674
 2675    cx.set_state("here is some text    ˇwith a space");
 2676    cx.update_editor(|editor, window, cx| {
 2677        editor.delete_to_previous_word_start(
 2678            &DeleteToPreviousWordStart {
 2679                ignore_newlines: false,
 2680                ignore_brackets: false,
 2681            },
 2682            window,
 2683            cx,
 2684        );
 2685    });
 2686    cx.assert_editor_state("here is some textˇwith a space");
 2687
 2688    cx.set_state("here is some textˇ    with a space");
 2689    cx.update_editor(|editor, window, cx| {
 2690        editor.delete_to_next_word_end(
 2691            &DeleteToNextWordEnd {
 2692                ignore_newlines: false,
 2693                ignore_brackets: true,
 2694            },
 2695            window,
 2696            cx,
 2697        );
 2698    });
 2699    // Same happens in the other direction.
 2700    cx.assert_editor_state("here is some textˇwith a space");
 2701
 2702    cx.set_state("here is some textˇ    with a space");
 2703    cx.update_editor(|editor, window, cx| {
 2704        editor.delete_to_next_word_end(
 2705            &DeleteToNextWordEnd {
 2706                ignore_newlines: false,
 2707                ignore_brackets: false,
 2708            },
 2709            window,
 2710            cx,
 2711        );
 2712    });
 2713    cx.assert_editor_state("here is some textˇwith a space");
 2714
 2715    cx.set_state("here is some textˇ    with a space");
 2716    cx.update_editor(|editor, window, cx| {
 2717        editor.delete_to_next_word_end(
 2718            &DeleteToNextWordEnd {
 2719                ignore_newlines: true,
 2720                ignore_brackets: false,
 2721            },
 2722            window,
 2723            cx,
 2724        );
 2725    });
 2726    cx.assert_editor_state("here is some textˇwith a space");
 2727    cx.update_editor(|editor, window, cx| {
 2728        editor.delete_to_previous_word_start(
 2729            &DeleteToPreviousWordStart {
 2730                ignore_newlines: true,
 2731                ignore_brackets: false,
 2732            },
 2733            window,
 2734            cx,
 2735        );
 2736    });
 2737    cx.assert_editor_state("here is some ˇwith a space");
 2738    cx.update_editor(|editor, window, cx| {
 2739        editor.delete_to_previous_word_start(
 2740            &DeleteToPreviousWordStart {
 2741                ignore_newlines: true,
 2742                ignore_brackets: false,
 2743            },
 2744            window,
 2745            cx,
 2746        );
 2747    });
 2748    // Single whitespaces are removed with the word behind them.
 2749    cx.assert_editor_state("here is ˇwith a space");
 2750    cx.update_editor(|editor, window, cx| {
 2751        editor.delete_to_previous_word_start(
 2752            &DeleteToPreviousWordStart {
 2753                ignore_newlines: true,
 2754                ignore_brackets: false,
 2755            },
 2756            window,
 2757            cx,
 2758        );
 2759    });
 2760    cx.assert_editor_state("here ˇwith a space");
 2761    cx.update_editor(|editor, window, cx| {
 2762        editor.delete_to_previous_word_start(
 2763            &DeleteToPreviousWordStart {
 2764                ignore_newlines: true,
 2765                ignore_brackets: false,
 2766            },
 2767            window,
 2768            cx,
 2769        );
 2770    });
 2771    cx.assert_editor_state("ˇwith a space");
 2772    cx.update_editor(|editor, window, cx| {
 2773        editor.delete_to_previous_word_start(
 2774            &DeleteToPreviousWordStart {
 2775                ignore_newlines: true,
 2776                ignore_brackets: false,
 2777            },
 2778            window,
 2779            cx,
 2780        );
 2781    });
 2782    cx.assert_editor_state("ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_next_word_end(
 2785            &DeleteToNextWordEnd {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    // Same happens in the other direction.
 2794    cx.assert_editor_state("ˇ a space");
 2795    cx.update_editor(|editor, window, cx| {
 2796        editor.delete_to_next_word_end(
 2797            &DeleteToNextWordEnd {
 2798                ignore_newlines: true,
 2799                ignore_brackets: false,
 2800            },
 2801            window,
 2802            cx,
 2803        );
 2804    });
 2805    cx.assert_editor_state("ˇ space");
 2806    cx.update_editor(|editor, window, cx| {
 2807        editor.delete_to_next_word_end(
 2808            &DeleteToNextWordEnd {
 2809                ignore_newlines: true,
 2810                ignore_brackets: false,
 2811            },
 2812            window,
 2813            cx,
 2814        );
 2815    });
 2816    cx.assert_editor_state("ˇ");
 2817    cx.update_editor(|editor, window, cx| {
 2818        editor.delete_to_next_word_end(
 2819            &DeleteToNextWordEnd {
 2820                ignore_newlines: true,
 2821                ignore_brackets: false,
 2822            },
 2823            window,
 2824            cx,
 2825        );
 2826    });
 2827    cx.assert_editor_state("ˇ");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_previous_word_start(
 2830            &DeleteToPreviousWordStart {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ");
 2839}
 2840
 2841#[gpui::test]
 2842async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2843    init_test(cx, |_| {});
 2844
 2845    let language = Arc::new(
 2846        Language::new(
 2847            LanguageConfig {
 2848                brackets: BracketPairConfig {
 2849                    pairs: vec![
 2850                        BracketPair {
 2851                            start: "\"".to_string(),
 2852                            end: "\"".to_string(),
 2853                            close: true,
 2854                            surround: true,
 2855                            newline: false,
 2856                        },
 2857                        BracketPair {
 2858                            start: "(".to_string(),
 2859                            end: ")".to_string(),
 2860                            close: true,
 2861                            surround: true,
 2862                            newline: true,
 2863                        },
 2864                    ],
 2865                    ..BracketPairConfig::default()
 2866                },
 2867                ..LanguageConfig::default()
 2868            },
 2869            Some(tree_sitter_rust::LANGUAGE.into()),
 2870        )
 2871        .with_brackets_query(
 2872            r#"
 2873                ("(" @open ")" @close)
 2874                ("\"" @open "\"" @close)
 2875            "#,
 2876        )
 2877        .unwrap(),
 2878    );
 2879
 2880    let mut cx = EditorTestContext::new(cx).await;
 2881    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2882
 2883    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2884    cx.update_editor(|editor, window, cx| {
 2885        editor.delete_to_previous_word_start(
 2886            &DeleteToPreviousWordStart {
 2887                ignore_newlines: true,
 2888                ignore_brackets: false,
 2889            },
 2890            window,
 2891            cx,
 2892        );
 2893    });
 2894    // Deletion stops before brackets if asked to not ignore them.
 2895    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2896    cx.update_editor(|editor, window, cx| {
 2897        editor.delete_to_previous_word_start(
 2898            &DeleteToPreviousWordStart {
 2899                ignore_newlines: true,
 2900                ignore_brackets: false,
 2901            },
 2902            window,
 2903            cx,
 2904        );
 2905    });
 2906    // Deletion has to remove a single bracket and then stop again.
 2907    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2908
 2909    cx.update_editor(|editor, window, cx| {
 2910        editor.delete_to_previous_word_start(
 2911            &DeleteToPreviousWordStart {
 2912                ignore_newlines: true,
 2913                ignore_brackets: false,
 2914            },
 2915            window,
 2916            cx,
 2917        );
 2918    });
 2919    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2920
 2921    cx.update_editor(|editor, window, cx| {
 2922        editor.delete_to_previous_word_start(
 2923            &DeleteToPreviousWordStart {
 2924                ignore_newlines: true,
 2925                ignore_brackets: false,
 2926            },
 2927            window,
 2928            cx,
 2929        );
 2930    });
 2931    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2932
 2933    cx.update_editor(|editor, window, cx| {
 2934        editor.delete_to_previous_word_start(
 2935            &DeleteToPreviousWordStart {
 2936                ignore_newlines: true,
 2937                ignore_brackets: false,
 2938            },
 2939            window,
 2940            cx,
 2941        );
 2942    });
 2943    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2944
 2945    cx.update_editor(|editor, window, cx| {
 2946        editor.delete_to_next_word_end(
 2947            &DeleteToNextWordEnd {
 2948                ignore_newlines: true,
 2949                ignore_brackets: false,
 2950            },
 2951            window,
 2952            cx,
 2953        );
 2954    });
 2955    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2956    cx.assert_editor_state(r#"ˇ");"#);
 2957
 2958    cx.update_editor(|editor, window, cx| {
 2959        editor.delete_to_next_word_end(
 2960            &DeleteToNextWordEnd {
 2961                ignore_newlines: true,
 2962                ignore_brackets: false,
 2963            },
 2964            window,
 2965            cx,
 2966        );
 2967    });
 2968    cx.assert_editor_state(r#"ˇ"#);
 2969
 2970    cx.update_editor(|editor, window, cx| {
 2971        editor.delete_to_next_word_end(
 2972            &DeleteToNextWordEnd {
 2973                ignore_newlines: true,
 2974                ignore_brackets: false,
 2975            },
 2976            window,
 2977            cx,
 2978        );
 2979    });
 2980    cx.assert_editor_state(r#"ˇ"#);
 2981
 2982    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2983    cx.update_editor(|editor, window, cx| {
 2984        editor.delete_to_previous_word_start(
 2985            &DeleteToPreviousWordStart {
 2986                ignore_newlines: true,
 2987                ignore_brackets: true,
 2988            },
 2989            window,
 2990            cx,
 2991        );
 2992    });
 2993    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2994}
 2995
 2996#[gpui::test]
 2997fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2998    init_test(cx, |_| {});
 2999
 3000    let editor = cx.add_window(|window, cx| {
 3001        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3002        build_editor(buffer, window, cx)
 3003    });
 3004    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3005        ignore_newlines: false,
 3006        ignore_brackets: false,
 3007    };
 3008    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3009        ignore_newlines: true,
 3010        ignore_brackets: false,
 3011    };
 3012
 3013    _ = editor.update(cx, |editor, window, cx| {
 3014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3015            s.select_display_ranges([
 3016                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3017            ])
 3018        });
 3019        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3020        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3021        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3022        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3023        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3024        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3025        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3026        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3027        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3028        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3029        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3030        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3031    });
 3032}
 3033
 3034#[gpui::test]
 3035fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3036    init_test(cx, |_| {});
 3037
 3038    let editor = cx.add_window(|window, cx| {
 3039        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3040        build_editor(buffer, window, cx)
 3041    });
 3042    let del_to_next_word_end = DeleteToNextWordEnd {
 3043        ignore_newlines: false,
 3044        ignore_brackets: false,
 3045    };
 3046    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3047        ignore_newlines: true,
 3048        ignore_brackets: false,
 3049    };
 3050
 3051    _ = editor.update(cx, |editor, window, cx| {
 3052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3053            s.select_display_ranges([
 3054                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3055            ])
 3056        });
 3057        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3058        assert_eq!(
 3059            editor.buffer.read(cx).read(cx).text(),
 3060            "one\n   two\nthree\n   four"
 3061        );
 3062        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3063        assert_eq!(
 3064            editor.buffer.read(cx).read(cx).text(),
 3065            "\n   two\nthree\n   four"
 3066        );
 3067        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3068        assert_eq!(
 3069            editor.buffer.read(cx).read(cx).text(),
 3070            "two\nthree\n   four"
 3071        );
 3072        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3073        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3074        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3075        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3076        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3077        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3078        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3079        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3080    });
 3081}
 3082
 3083#[gpui::test]
 3084fn test_newline(cx: &mut TestAppContext) {
 3085    init_test(cx, |_| {});
 3086
 3087    let editor = cx.add_window(|window, cx| {
 3088        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3089        build_editor(buffer, window, cx)
 3090    });
 3091
 3092    _ = editor.update(cx, |editor, window, cx| {
 3093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3094            s.select_display_ranges([
 3095                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3096                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3097                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3098            ])
 3099        });
 3100
 3101        editor.newline(&Newline, window, cx);
 3102        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3103    });
 3104}
 3105
 3106#[gpui::test]
 3107async fn test_newline_yaml(cx: &mut TestAppContext) {
 3108    init_test(cx, |_| {});
 3109
 3110    let mut cx = EditorTestContext::new(cx).await;
 3111    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3112    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3113
 3114    // Object (between 2 fields)
 3115    cx.set_state(indoc! {"
 3116    test:ˇ
 3117    hello: bye"});
 3118    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3119    cx.assert_editor_state(indoc! {"
 3120    test:
 3121        ˇ
 3122    hello: bye"});
 3123
 3124    // Object (first and single line)
 3125    cx.set_state(indoc! {"
 3126    test:ˇ"});
 3127    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3128    cx.assert_editor_state(indoc! {"
 3129    test:
 3130        ˇ"});
 3131
 3132    // Array with objects (after first element)
 3133    cx.set_state(indoc! {"
 3134    test:
 3135        - foo: barˇ"});
 3136    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3137    cx.assert_editor_state(indoc! {"
 3138    test:
 3139        - foo: bar
 3140        ˇ"});
 3141
 3142    // Array with objects and comment
 3143    cx.set_state(indoc! {"
 3144    test:
 3145        - foo: bar
 3146        - bar: # testˇ"});
 3147    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3148    cx.assert_editor_state(indoc! {"
 3149    test:
 3150        - foo: bar
 3151        - bar: # test
 3152            ˇ"});
 3153
 3154    // Array with objects (after second element)
 3155    cx.set_state(indoc! {"
 3156    test:
 3157        - foo: bar
 3158        - bar: fooˇ"});
 3159    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3160    cx.assert_editor_state(indoc! {"
 3161    test:
 3162        - foo: bar
 3163        - bar: foo
 3164        ˇ"});
 3165
 3166    // Array with strings (after first element)
 3167    cx.set_state(indoc! {"
 3168    test:
 3169        - fooˇ"});
 3170    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3171    cx.assert_editor_state(indoc! {"
 3172    test:
 3173        - foo
 3174        ˇ"});
 3175}
 3176
 3177#[gpui::test]
 3178fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3179    init_test(cx, |_| {});
 3180
 3181    let editor = cx.add_window(|window, cx| {
 3182        let buffer = MultiBuffer::build_simple(
 3183            "
 3184                a
 3185                b(
 3186                    X
 3187                )
 3188                c(
 3189                    X
 3190                )
 3191            "
 3192            .unindent()
 3193            .as_str(),
 3194            cx,
 3195        );
 3196        let mut editor = build_editor(buffer, window, cx);
 3197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3198            s.select_ranges([
 3199                Point::new(2, 4)..Point::new(2, 5),
 3200                Point::new(5, 4)..Point::new(5, 5),
 3201            ])
 3202        });
 3203        editor
 3204    });
 3205
 3206    _ = editor.update(cx, |editor, window, cx| {
 3207        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3208        editor.buffer.update(cx, |buffer, cx| {
 3209            buffer.edit(
 3210                [
 3211                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3212                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3213                ],
 3214                None,
 3215                cx,
 3216            );
 3217            assert_eq!(
 3218                buffer.read(cx).text(),
 3219                "
 3220                    a
 3221                    b()
 3222                    c()
 3223                "
 3224                .unindent()
 3225            );
 3226        });
 3227        assert_eq!(
 3228            editor.selections.ranges(&editor.display_snapshot(cx)),
 3229            &[
 3230                Point::new(1, 2)..Point::new(1, 2),
 3231                Point::new(2, 2)..Point::new(2, 2),
 3232            ],
 3233        );
 3234
 3235        editor.newline(&Newline, window, cx);
 3236        assert_eq!(
 3237            editor.text(cx),
 3238            "
 3239                a
 3240                b(
 3241                )
 3242                c(
 3243                )
 3244            "
 3245            .unindent()
 3246        );
 3247
 3248        // The selections are moved after the inserted newlines
 3249        assert_eq!(
 3250            editor.selections.ranges(&editor.display_snapshot(cx)),
 3251            &[
 3252                Point::new(2, 0)..Point::new(2, 0),
 3253                Point::new(4, 0)..Point::new(4, 0),
 3254            ],
 3255        );
 3256    });
 3257}
 3258
 3259#[gpui::test]
 3260async fn test_newline_above(cx: &mut TestAppContext) {
 3261    init_test(cx, |settings| {
 3262        settings.defaults.tab_size = NonZeroU32::new(4)
 3263    });
 3264
 3265    let language = Arc::new(
 3266        Language::new(
 3267            LanguageConfig::default(),
 3268            Some(tree_sitter_rust::LANGUAGE.into()),
 3269        )
 3270        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3271        .unwrap(),
 3272    );
 3273
 3274    let mut cx = EditorTestContext::new(cx).await;
 3275    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3276    cx.set_state(indoc! {"
 3277        const a: ˇA = (
 3278 3279                «const_functionˇ»(ˇ),
 3280                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3281 3282        ˇ);ˇ
 3283    "});
 3284
 3285    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3286    cx.assert_editor_state(indoc! {"
 3287        ˇ
 3288        const a: A = (
 3289            ˇ
 3290            (
 3291                ˇ
 3292                ˇ
 3293                const_function(),
 3294                ˇ
 3295                ˇ
 3296                ˇ
 3297                ˇ
 3298                something_else,
 3299                ˇ
 3300            )
 3301            ˇ
 3302            ˇ
 3303        );
 3304    "});
 3305}
 3306
 3307#[gpui::test]
 3308async fn test_newline_below(cx: &mut TestAppContext) {
 3309    init_test(cx, |settings| {
 3310        settings.defaults.tab_size = NonZeroU32::new(4)
 3311    });
 3312
 3313    let language = Arc::new(
 3314        Language::new(
 3315            LanguageConfig::default(),
 3316            Some(tree_sitter_rust::LANGUAGE.into()),
 3317        )
 3318        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3319        .unwrap(),
 3320    );
 3321
 3322    let mut cx = EditorTestContext::new(cx).await;
 3323    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3324    cx.set_state(indoc! {"
 3325        const a: ˇA = (
 3326 3327                «const_functionˇ»(ˇ),
 3328                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3329 3330        ˇ);ˇ
 3331    "});
 3332
 3333    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3334    cx.assert_editor_state(indoc! {"
 3335        const a: A = (
 3336            ˇ
 3337            (
 3338                ˇ
 3339                const_function(),
 3340                ˇ
 3341                ˇ
 3342                something_else,
 3343                ˇ
 3344                ˇ
 3345                ˇ
 3346                ˇ
 3347            )
 3348            ˇ
 3349        );
 3350        ˇ
 3351        ˇ
 3352    "});
 3353}
 3354
 3355#[gpui::test]
 3356async fn test_newline_comments(cx: &mut TestAppContext) {
 3357    init_test(cx, |settings| {
 3358        settings.defaults.tab_size = NonZeroU32::new(4)
 3359    });
 3360
 3361    let language = Arc::new(Language::new(
 3362        LanguageConfig {
 3363            line_comments: vec!["// ".into()],
 3364            ..LanguageConfig::default()
 3365        },
 3366        None,
 3367    ));
 3368    {
 3369        let mut cx = EditorTestContext::new(cx).await;
 3370        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3371        cx.set_state(indoc! {"
 3372        // Fooˇ
 3373    "});
 3374
 3375        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3376        cx.assert_editor_state(indoc! {"
 3377        // Foo
 3378        // ˇ
 3379    "});
 3380        // Ensure that we add comment prefix when existing line contains space
 3381        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3382        cx.assert_editor_state(
 3383            indoc! {"
 3384        // Foo
 3385        //s
 3386        // ˇ
 3387    "}
 3388            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3389            .as_str(),
 3390        );
 3391        // Ensure that we add comment prefix when existing line does not contain space
 3392        cx.set_state(indoc! {"
 3393        // Foo
 3394        //ˇ
 3395    "});
 3396        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3397        cx.assert_editor_state(indoc! {"
 3398        // Foo
 3399        //
 3400        // ˇ
 3401    "});
 3402        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3403        cx.set_state(indoc! {"
 3404        ˇ// Foo
 3405    "});
 3406        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3407        cx.assert_editor_state(indoc! {"
 3408
 3409        ˇ// Foo
 3410    "});
 3411    }
 3412    // Ensure that comment continuations can be disabled.
 3413    update_test_language_settings(cx, |settings| {
 3414        settings.defaults.extend_comment_on_newline = Some(false);
 3415    });
 3416    let mut cx = EditorTestContext::new(cx).await;
 3417    cx.set_state(indoc! {"
 3418        // Fooˇ
 3419    "});
 3420    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3421    cx.assert_editor_state(indoc! {"
 3422        // Foo
 3423        ˇ
 3424    "});
 3425}
 3426
 3427#[gpui::test]
 3428async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3429    init_test(cx, |settings| {
 3430        settings.defaults.tab_size = NonZeroU32::new(4)
 3431    });
 3432
 3433    let language = Arc::new(Language::new(
 3434        LanguageConfig {
 3435            line_comments: vec!["// ".into(), "/// ".into()],
 3436            ..LanguageConfig::default()
 3437        },
 3438        None,
 3439    ));
 3440    {
 3441        let mut cx = EditorTestContext::new(cx).await;
 3442        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3443        cx.set_state(indoc! {"
 3444        //ˇ
 3445    "});
 3446        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3447        cx.assert_editor_state(indoc! {"
 3448        //
 3449        // ˇ
 3450    "});
 3451
 3452        cx.set_state(indoc! {"
 3453        ///ˇ
 3454    "});
 3455        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3456        cx.assert_editor_state(indoc! {"
 3457        ///
 3458        /// ˇ
 3459    "});
 3460    }
 3461}
 3462
 3463#[gpui::test]
 3464async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3465    init_test(cx, |settings| {
 3466        settings.defaults.tab_size = NonZeroU32::new(4)
 3467    });
 3468
 3469    let language = Arc::new(
 3470        Language::new(
 3471            LanguageConfig {
 3472                documentation_comment: Some(language::BlockCommentConfig {
 3473                    start: "/**".into(),
 3474                    end: "*/".into(),
 3475                    prefix: "* ".into(),
 3476                    tab_size: 1,
 3477                }),
 3478
 3479                ..LanguageConfig::default()
 3480            },
 3481            Some(tree_sitter_rust::LANGUAGE.into()),
 3482        )
 3483        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3484        .unwrap(),
 3485    );
 3486
 3487    {
 3488        let mut cx = EditorTestContext::new(cx).await;
 3489        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3490        cx.set_state(indoc! {"
 3491        /**ˇ
 3492    "});
 3493
 3494        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3495        cx.assert_editor_state(indoc! {"
 3496        /**
 3497         * ˇ
 3498    "});
 3499        // Ensure that if cursor is before the comment start,
 3500        // we do not actually insert a comment prefix.
 3501        cx.set_state(indoc! {"
 3502        ˇ/**
 3503    "});
 3504        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3505        cx.assert_editor_state(indoc! {"
 3506
 3507        ˇ/**
 3508    "});
 3509        // Ensure that if cursor is between it doesn't add comment prefix.
 3510        cx.set_state(indoc! {"
 3511        /*ˇ*
 3512    "});
 3513        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3514        cx.assert_editor_state(indoc! {"
 3515        /*
 3516        ˇ*
 3517    "});
 3518        // Ensure that if suffix exists on same line after cursor it adds new line.
 3519        cx.set_state(indoc! {"
 3520        /**ˇ*/
 3521    "});
 3522        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3523        cx.assert_editor_state(indoc! {"
 3524        /**
 3525         * ˇ
 3526         */
 3527    "});
 3528        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3529        cx.set_state(indoc! {"
 3530        /**ˇ */
 3531    "});
 3532        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3533        cx.assert_editor_state(indoc! {"
 3534        /**
 3535         * ˇ
 3536         */
 3537    "});
 3538        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3539        cx.set_state(indoc! {"
 3540        /** ˇ*/
 3541    "});
 3542        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3543        cx.assert_editor_state(
 3544            indoc! {"
 3545        /**s
 3546         * ˇ
 3547         */
 3548    "}
 3549            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3550            .as_str(),
 3551        );
 3552        // Ensure that delimiter space is preserved when newline on already
 3553        // spaced delimiter.
 3554        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3555        cx.assert_editor_state(
 3556            indoc! {"
 3557        /**s
 3558         *s
 3559         * ˇ
 3560         */
 3561    "}
 3562            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3563            .as_str(),
 3564        );
 3565        // Ensure that delimiter space is preserved when space is not
 3566        // on existing delimiter.
 3567        cx.set_state(indoc! {"
 3568        /**
 3569 3570         */
 3571    "});
 3572        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3573        cx.assert_editor_state(indoc! {"
 3574        /**
 3575         *
 3576         * ˇ
 3577         */
 3578    "});
 3579        // Ensure that if suffix exists on same line after cursor it
 3580        // doesn't add extra new line if prefix is not on same line.
 3581        cx.set_state(indoc! {"
 3582        /**
 3583        ˇ*/
 3584    "});
 3585        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3586        cx.assert_editor_state(indoc! {"
 3587        /**
 3588
 3589        ˇ*/
 3590    "});
 3591        // Ensure that it detects suffix after existing prefix.
 3592        cx.set_state(indoc! {"
 3593        /**ˇ/
 3594    "});
 3595        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3596        cx.assert_editor_state(indoc! {"
 3597        /**
 3598        ˇ/
 3599    "});
 3600        // Ensure that if suffix exists on same line before
 3601        // cursor it does not add comment prefix.
 3602        cx.set_state(indoc! {"
 3603        /** */ˇ
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /** */
 3608        ˇ
 3609    "});
 3610        // Ensure that if suffix exists on same line before
 3611        // cursor it does not add comment prefix.
 3612        cx.set_state(indoc! {"
 3613        /**
 3614         *
 3615         */ˇ
 3616    "});
 3617        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3618        cx.assert_editor_state(indoc! {"
 3619        /**
 3620         *
 3621         */
 3622         ˇ
 3623    "});
 3624
 3625        // Ensure that inline comment followed by code
 3626        // doesn't add comment prefix on newline
 3627        cx.set_state(indoc! {"
 3628        /** */ textˇ
 3629    "});
 3630        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3631        cx.assert_editor_state(indoc! {"
 3632        /** */ text
 3633        ˇ
 3634    "});
 3635
 3636        // Ensure that text after comment end tag
 3637        // doesn't add comment prefix on newline
 3638        cx.set_state(indoc! {"
 3639        /**
 3640         *
 3641         */ˇtext
 3642    "});
 3643        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3644        cx.assert_editor_state(indoc! {"
 3645        /**
 3646         *
 3647         */
 3648         ˇtext
 3649    "});
 3650
 3651        // Ensure if not comment block it doesn't
 3652        // add comment prefix on newline
 3653        cx.set_state(indoc! {"
 3654        * textˇ
 3655    "});
 3656        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3657        cx.assert_editor_state(indoc! {"
 3658        * text
 3659        ˇ
 3660    "});
 3661    }
 3662    // Ensure that comment continuations can be disabled.
 3663    update_test_language_settings(cx, |settings| {
 3664        settings.defaults.extend_comment_on_newline = Some(false);
 3665    });
 3666    let mut cx = EditorTestContext::new(cx).await;
 3667    cx.set_state(indoc! {"
 3668        /**ˇ
 3669    "});
 3670    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3671    cx.assert_editor_state(indoc! {"
 3672        /**
 3673        ˇ
 3674    "});
 3675}
 3676
 3677#[gpui::test]
 3678async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3679    init_test(cx, |settings| {
 3680        settings.defaults.tab_size = NonZeroU32::new(4)
 3681    });
 3682
 3683    let lua_language = Arc::new(Language::new(
 3684        LanguageConfig {
 3685            line_comments: vec!["--".into()],
 3686            block_comment: Some(language::BlockCommentConfig {
 3687                start: "--[[".into(),
 3688                prefix: "".into(),
 3689                end: "]]".into(),
 3690                tab_size: 0,
 3691            }),
 3692            ..LanguageConfig::default()
 3693        },
 3694        None,
 3695    ));
 3696
 3697    let mut cx = EditorTestContext::new(cx).await;
 3698    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3699
 3700    // Line with line comment should extend
 3701    cx.set_state(indoc! {"
 3702        --ˇ
 3703    "});
 3704    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3705    cx.assert_editor_state(indoc! {"
 3706        --
 3707        --ˇ
 3708    "});
 3709
 3710    // Line with block comment that matches line comment should not extend
 3711    cx.set_state(indoc! {"
 3712        --[[ˇ
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        --[[
 3717        ˇ
 3718    "});
 3719}
 3720
 3721#[gpui::test]
 3722fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3723    init_test(cx, |_| {});
 3724
 3725    let editor = cx.add_window(|window, cx| {
 3726        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3727        let mut editor = build_editor(buffer, window, cx);
 3728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3729            s.select_ranges([
 3730                MultiBufferOffset(3)..MultiBufferOffset(4),
 3731                MultiBufferOffset(11)..MultiBufferOffset(12),
 3732                MultiBufferOffset(19)..MultiBufferOffset(20),
 3733            ])
 3734        });
 3735        editor
 3736    });
 3737
 3738    _ = editor.update(cx, |editor, window, cx| {
 3739        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3740        editor.buffer.update(cx, |buffer, cx| {
 3741            buffer.edit(
 3742                [
 3743                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3744                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3745                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3746                ],
 3747                None,
 3748                cx,
 3749            );
 3750            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3751        });
 3752        assert_eq!(
 3753            editor.selections.ranges(&editor.display_snapshot(cx)),
 3754            &[
 3755                MultiBufferOffset(2)..MultiBufferOffset(2),
 3756                MultiBufferOffset(7)..MultiBufferOffset(7),
 3757                MultiBufferOffset(12)..MultiBufferOffset(12)
 3758            ],
 3759        );
 3760
 3761        editor.insert("Z", window, cx);
 3762        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3763
 3764        // The selections are moved after the inserted characters
 3765        assert_eq!(
 3766            editor.selections.ranges(&editor.display_snapshot(cx)),
 3767            &[
 3768                MultiBufferOffset(3)..MultiBufferOffset(3),
 3769                MultiBufferOffset(9)..MultiBufferOffset(9),
 3770                MultiBufferOffset(15)..MultiBufferOffset(15)
 3771            ],
 3772        );
 3773    });
 3774}
 3775
 3776#[gpui::test]
 3777async fn test_tab(cx: &mut TestAppContext) {
 3778    init_test(cx, |settings| {
 3779        settings.defaults.tab_size = NonZeroU32::new(3)
 3780    });
 3781
 3782    let mut cx = EditorTestContext::new(cx).await;
 3783    cx.set_state(indoc! {"
 3784        ˇabˇc
 3785        ˇ🏀ˇ🏀ˇefg
 3786 3787    "});
 3788    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3789    cx.assert_editor_state(indoc! {"
 3790           ˇab ˇc
 3791           ˇ🏀  ˇ🏀  ˇefg
 3792        d  ˇ
 3793    "});
 3794
 3795    cx.set_state(indoc! {"
 3796        a
 3797        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3798    "});
 3799    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3800    cx.assert_editor_state(indoc! {"
 3801        a
 3802           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3803    "});
 3804}
 3805
 3806#[gpui::test]
 3807async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3808    init_test(cx, |_| {});
 3809
 3810    let mut cx = EditorTestContext::new(cx).await;
 3811    let language = Arc::new(
 3812        Language::new(
 3813            LanguageConfig::default(),
 3814            Some(tree_sitter_rust::LANGUAGE.into()),
 3815        )
 3816        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3817        .unwrap(),
 3818    );
 3819    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3820
 3821    // test when all cursors are not at suggested indent
 3822    // then simply move to their suggested indent location
 3823    cx.set_state(indoc! {"
 3824        const a: B = (
 3825            c(
 3826        ˇ
 3827        ˇ    )
 3828        );
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        const a: B = (
 3833            c(
 3834                ˇ
 3835            ˇ)
 3836        );
 3837    "});
 3838
 3839    // test cursor already at suggested indent not moving when
 3840    // other cursors are yet to reach their suggested indents
 3841    cx.set_state(indoc! {"
 3842        ˇ
 3843        const a: B = (
 3844            c(
 3845                d(
 3846        ˇ
 3847                )
 3848        ˇ
 3849        ˇ    )
 3850        );
 3851    "});
 3852    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3853    cx.assert_editor_state(indoc! {"
 3854        ˇ
 3855        const a: B = (
 3856            c(
 3857                d(
 3858                    ˇ
 3859                )
 3860                ˇ
 3861            ˇ)
 3862        );
 3863    "});
 3864    // test when all cursors are at suggested indent then tab is inserted
 3865    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3866    cx.assert_editor_state(indoc! {"
 3867            ˇ
 3868        const a: B = (
 3869            c(
 3870                d(
 3871                        ˇ
 3872                )
 3873                    ˇ
 3874                ˇ)
 3875        );
 3876    "});
 3877
 3878    // test when current indent is less than suggested indent,
 3879    // we adjust line to match suggested indent and move cursor to it
 3880    //
 3881    // when no other cursor is at word boundary, all of them should move
 3882    cx.set_state(indoc! {"
 3883        const a: B = (
 3884            c(
 3885                d(
 3886        ˇ
 3887        ˇ   )
 3888        ˇ   )
 3889        );
 3890    "});
 3891    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3892    cx.assert_editor_state(indoc! {"
 3893        const a: B = (
 3894            c(
 3895                d(
 3896                    ˇ
 3897                ˇ)
 3898            ˇ)
 3899        );
 3900    "});
 3901
 3902    // test when current indent is less than suggested indent,
 3903    // we adjust line to match suggested indent and move cursor to it
 3904    //
 3905    // when some other cursor is at word boundary, it should not move
 3906    cx.set_state(indoc! {"
 3907        const a: B = (
 3908            c(
 3909                d(
 3910        ˇ
 3911        ˇ   )
 3912           ˇ)
 3913        );
 3914    "});
 3915    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3916    cx.assert_editor_state(indoc! {"
 3917        const a: B = (
 3918            c(
 3919                d(
 3920                    ˇ
 3921                ˇ)
 3922            ˇ)
 3923        );
 3924    "});
 3925
 3926    // test when current indent is more than suggested indent,
 3927    // we just move cursor to current indent instead of suggested indent
 3928    //
 3929    // when no other cursor is at word boundary, all of them should move
 3930    cx.set_state(indoc! {"
 3931        const a: B = (
 3932            c(
 3933                d(
 3934        ˇ
 3935        ˇ                )
 3936        ˇ   )
 3937        );
 3938    "});
 3939    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3940    cx.assert_editor_state(indoc! {"
 3941        const a: B = (
 3942            c(
 3943                d(
 3944                    ˇ
 3945                        ˇ)
 3946            ˇ)
 3947        );
 3948    "});
 3949    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3950    cx.assert_editor_state(indoc! {"
 3951        const a: B = (
 3952            c(
 3953                d(
 3954                        ˇ
 3955                            ˇ)
 3956                ˇ)
 3957        );
 3958    "});
 3959
 3960    // test when current indent is more than suggested indent,
 3961    // we just move cursor to current indent instead of suggested indent
 3962    //
 3963    // when some other cursor is at word boundary, it doesn't move
 3964    cx.set_state(indoc! {"
 3965        const a: B = (
 3966            c(
 3967                d(
 3968        ˇ
 3969        ˇ                )
 3970            ˇ)
 3971        );
 3972    "});
 3973    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3974    cx.assert_editor_state(indoc! {"
 3975        const a: B = (
 3976            c(
 3977                d(
 3978                    ˇ
 3979                        ˇ)
 3980            ˇ)
 3981        );
 3982    "});
 3983
 3984    // handle auto-indent when there are multiple cursors on the same line
 3985    cx.set_state(indoc! {"
 3986        const a: B = (
 3987            c(
 3988        ˇ    ˇ
 3989        ˇ    )
 3990        );
 3991    "});
 3992    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        const a: B = (
 3995            c(
 3996                ˇ
 3997            ˇ)
 3998        );
 3999    "});
 4000}
 4001
 4002#[gpui::test]
 4003async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4004    init_test(cx, |settings| {
 4005        settings.defaults.tab_size = NonZeroU32::new(3)
 4006    });
 4007
 4008    let mut cx = EditorTestContext::new(cx).await;
 4009    cx.set_state(indoc! {"
 4010         ˇ
 4011        \t ˇ
 4012        \t  ˇ
 4013        \t   ˇ
 4014         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4015    "});
 4016
 4017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019           ˇ
 4020        \t   ˇ
 4021        \t   ˇ
 4022        \t      ˇ
 4023         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4024    "});
 4025}
 4026
 4027#[gpui::test]
 4028async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4029    init_test(cx, |settings| {
 4030        settings.defaults.tab_size = NonZeroU32::new(4)
 4031    });
 4032
 4033    let language = Arc::new(
 4034        Language::new(
 4035            LanguageConfig::default(),
 4036            Some(tree_sitter_rust::LANGUAGE.into()),
 4037        )
 4038        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4039        .unwrap(),
 4040    );
 4041
 4042    let mut cx = EditorTestContext::new(cx).await;
 4043    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4044    cx.set_state(indoc! {"
 4045        fn a() {
 4046            if b {
 4047        \t ˇc
 4048            }
 4049        }
 4050    "});
 4051
 4052    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4053    cx.assert_editor_state(indoc! {"
 4054        fn a() {
 4055            if b {
 4056                ˇc
 4057            }
 4058        }
 4059    "});
 4060}
 4061
 4062#[gpui::test]
 4063async fn test_indent_outdent(cx: &mut TestAppContext) {
 4064    init_test(cx, |settings| {
 4065        settings.defaults.tab_size = NonZeroU32::new(4);
 4066    });
 4067
 4068    let mut cx = EditorTestContext::new(cx).await;
 4069
 4070    cx.set_state(indoc! {"
 4071          «oneˇ» «twoˇ»
 4072        three
 4073         four
 4074    "});
 4075    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4076    cx.assert_editor_state(indoc! {"
 4077            «oneˇ» «twoˇ»
 4078        three
 4079         four
 4080    "});
 4081
 4082    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4083    cx.assert_editor_state(indoc! {"
 4084        «oneˇ» «twoˇ»
 4085        three
 4086         four
 4087    "});
 4088
 4089    // select across line ending
 4090    cx.set_state(indoc! {"
 4091        one two
 4092        t«hree
 4093        ˇ» four
 4094    "});
 4095    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4096    cx.assert_editor_state(indoc! {"
 4097        one two
 4098            t«hree
 4099        ˇ» four
 4100    "});
 4101
 4102    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4103    cx.assert_editor_state(indoc! {"
 4104        one two
 4105        t«hree
 4106        ˇ» four
 4107    "});
 4108
 4109    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4110    cx.set_state(indoc! {"
 4111        one two
 4112        ˇthree
 4113            four
 4114    "});
 4115    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4116    cx.assert_editor_state(indoc! {"
 4117        one two
 4118            ˇthree
 4119            four
 4120    "});
 4121
 4122    cx.set_state(indoc! {"
 4123        one two
 4124        ˇ    three
 4125            four
 4126    "});
 4127    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4128    cx.assert_editor_state(indoc! {"
 4129        one two
 4130        ˇthree
 4131            four
 4132    "});
 4133}
 4134
 4135#[gpui::test]
 4136async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4137    // This is a regression test for issue #33761
 4138    init_test(cx, |_| {});
 4139
 4140    let mut cx = EditorTestContext::new(cx).await;
 4141    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4142    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4143
 4144    cx.set_state(
 4145        r#"ˇ#     ingress:
 4146ˇ#         api:
 4147ˇ#             enabled: false
 4148ˇ#             pathType: Prefix
 4149ˇ#           console:
 4150ˇ#               enabled: false
 4151ˇ#               pathType: Prefix
 4152"#,
 4153    );
 4154
 4155    // Press tab to indent all lines
 4156    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4157
 4158    cx.assert_editor_state(
 4159        r#"    ˇ#     ingress:
 4160    ˇ#         api:
 4161    ˇ#             enabled: false
 4162    ˇ#             pathType: Prefix
 4163    ˇ#           console:
 4164    ˇ#               enabled: false
 4165    ˇ#               pathType: Prefix
 4166"#,
 4167    );
 4168}
 4169
 4170#[gpui::test]
 4171async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4172    // This is a test to make sure our fix for issue #33761 didn't break anything
 4173    init_test(cx, |_| {});
 4174
 4175    let mut cx = EditorTestContext::new(cx).await;
 4176    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4177    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4178
 4179    cx.set_state(
 4180        r#"ˇingress:
 4181ˇ  api:
 4182ˇ    enabled: false
 4183ˇ    pathType: Prefix
 4184"#,
 4185    );
 4186
 4187    // Press tab to indent all lines
 4188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4189
 4190    cx.assert_editor_state(
 4191        r#"ˇingress:
 4192    ˇapi:
 4193        ˇenabled: false
 4194        ˇpathType: Prefix
 4195"#,
 4196    );
 4197}
 4198
 4199#[gpui::test]
 4200async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4201    init_test(cx, |settings| {
 4202        settings.defaults.hard_tabs = Some(true);
 4203    });
 4204
 4205    let mut cx = EditorTestContext::new(cx).await;
 4206
 4207    // select two ranges on one line
 4208    cx.set_state(indoc! {"
 4209        «oneˇ» «twoˇ»
 4210        three
 4211        four
 4212    "});
 4213    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4214    cx.assert_editor_state(indoc! {"
 4215        \t«oneˇ» «twoˇ»
 4216        three
 4217        four
 4218    "});
 4219    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4220    cx.assert_editor_state(indoc! {"
 4221        \t\t«oneˇ» «twoˇ»
 4222        three
 4223        four
 4224    "});
 4225    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4226    cx.assert_editor_state(indoc! {"
 4227        \t«oneˇ» «twoˇ»
 4228        three
 4229        four
 4230    "});
 4231    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4232    cx.assert_editor_state(indoc! {"
 4233        «oneˇ» «twoˇ»
 4234        three
 4235        four
 4236    "});
 4237
 4238    // select across a line ending
 4239    cx.set_state(indoc! {"
 4240        one two
 4241        t«hree
 4242        ˇ»four
 4243    "});
 4244    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4245    cx.assert_editor_state(indoc! {"
 4246        one two
 4247        \tt«hree
 4248        ˇ»four
 4249    "});
 4250    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4251    cx.assert_editor_state(indoc! {"
 4252        one two
 4253        \t\tt«hree
 4254        ˇ»four
 4255    "});
 4256    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4257    cx.assert_editor_state(indoc! {"
 4258        one two
 4259        \tt«hree
 4260        ˇ»four
 4261    "});
 4262    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4263    cx.assert_editor_state(indoc! {"
 4264        one two
 4265        t«hree
 4266        ˇ»four
 4267    "});
 4268
 4269    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4270    cx.set_state(indoc! {"
 4271        one two
 4272        ˇthree
 4273        four
 4274    "});
 4275    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4276    cx.assert_editor_state(indoc! {"
 4277        one two
 4278        ˇthree
 4279        four
 4280    "});
 4281    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4282    cx.assert_editor_state(indoc! {"
 4283        one two
 4284        \tˇthree
 4285        four
 4286    "});
 4287    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4288    cx.assert_editor_state(indoc! {"
 4289        one two
 4290        ˇthree
 4291        four
 4292    "});
 4293}
 4294
 4295#[gpui::test]
 4296fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4297    init_test(cx, |settings| {
 4298        settings.languages.0.extend([
 4299            (
 4300                "TOML".into(),
 4301                LanguageSettingsContent {
 4302                    tab_size: NonZeroU32::new(2),
 4303                    ..Default::default()
 4304                },
 4305            ),
 4306            (
 4307                "Rust".into(),
 4308                LanguageSettingsContent {
 4309                    tab_size: NonZeroU32::new(4),
 4310                    ..Default::default()
 4311                },
 4312            ),
 4313        ]);
 4314    });
 4315
 4316    let toml_language = Arc::new(Language::new(
 4317        LanguageConfig {
 4318            name: "TOML".into(),
 4319            ..Default::default()
 4320        },
 4321        None,
 4322    ));
 4323    let rust_language = Arc::new(Language::new(
 4324        LanguageConfig {
 4325            name: "Rust".into(),
 4326            ..Default::default()
 4327        },
 4328        None,
 4329    ));
 4330
 4331    let toml_buffer =
 4332        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4333    let rust_buffer =
 4334        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4335    let multibuffer = cx.new(|cx| {
 4336        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4337        multibuffer.push_excerpts(
 4338            toml_buffer.clone(),
 4339            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4340            cx,
 4341        );
 4342        multibuffer.push_excerpts(
 4343            rust_buffer.clone(),
 4344            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4345            cx,
 4346        );
 4347        multibuffer
 4348    });
 4349
 4350    cx.add_window(|window, cx| {
 4351        let mut editor = build_editor(multibuffer, window, cx);
 4352
 4353        assert_eq!(
 4354            editor.text(cx),
 4355            indoc! {"
 4356                a = 1
 4357                b = 2
 4358
 4359                const c: usize = 3;
 4360            "}
 4361        );
 4362
 4363        select_ranges(
 4364            &mut editor,
 4365            indoc! {"
 4366                «aˇ» = 1
 4367                b = 2
 4368
 4369                «const c:ˇ» usize = 3;
 4370            "},
 4371            window,
 4372            cx,
 4373        );
 4374
 4375        editor.tab(&Tab, window, cx);
 4376        assert_text_with_selections(
 4377            &mut editor,
 4378            indoc! {"
 4379                  «aˇ» = 1
 4380                b = 2
 4381
 4382                    «const c:ˇ» usize = 3;
 4383            "},
 4384            cx,
 4385        );
 4386        editor.backtab(&Backtab, window, cx);
 4387        assert_text_with_selections(
 4388            &mut editor,
 4389            indoc! {"
 4390                «aˇ» = 1
 4391                b = 2
 4392
 4393                «const c:ˇ» usize = 3;
 4394            "},
 4395            cx,
 4396        );
 4397
 4398        editor
 4399    });
 4400}
 4401
 4402#[gpui::test]
 4403async fn test_backspace(cx: &mut TestAppContext) {
 4404    init_test(cx, |_| {});
 4405
 4406    let mut cx = EditorTestContext::new(cx).await;
 4407
 4408    // Basic backspace
 4409    cx.set_state(indoc! {"
 4410        onˇe two three
 4411        fou«rˇ» five six
 4412        seven «ˇeight nine
 4413        »ten
 4414    "});
 4415    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4416    cx.assert_editor_state(indoc! {"
 4417        oˇe two three
 4418        fouˇ five six
 4419        seven ˇten
 4420    "});
 4421
 4422    // Test backspace inside and around indents
 4423    cx.set_state(indoc! {"
 4424        zero
 4425            ˇone
 4426                ˇtwo
 4427            ˇ ˇ ˇ  three
 4428        ˇ  ˇ  four
 4429    "});
 4430    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4431    cx.assert_editor_state(indoc! {"
 4432        zero
 4433        ˇone
 4434            ˇtwo
 4435        ˇ  threeˇ  four
 4436    "});
 4437}
 4438
 4439#[gpui::test]
 4440async fn test_delete(cx: &mut TestAppContext) {
 4441    init_test(cx, |_| {});
 4442
 4443    let mut cx = EditorTestContext::new(cx).await;
 4444    cx.set_state(indoc! {"
 4445        onˇe two three
 4446        fou«rˇ» five six
 4447        seven «ˇeight nine
 4448        »ten
 4449    "});
 4450    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4451    cx.assert_editor_state(indoc! {"
 4452        onˇ two three
 4453        fouˇ five six
 4454        seven ˇten
 4455    "});
 4456}
 4457
 4458#[gpui::test]
 4459fn test_delete_line(cx: &mut TestAppContext) {
 4460    init_test(cx, |_| {});
 4461
 4462    let editor = cx.add_window(|window, cx| {
 4463        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4464        build_editor(buffer, window, cx)
 4465    });
 4466    _ = editor.update(cx, |editor, window, cx| {
 4467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4468            s.select_display_ranges([
 4469                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4470                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4471                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4472            ])
 4473        });
 4474        editor.delete_line(&DeleteLine, window, cx);
 4475        assert_eq!(editor.display_text(cx), "ghi");
 4476        assert_eq!(
 4477            display_ranges(editor, cx),
 4478            vec![
 4479                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4480                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4481            ]
 4482        );
 4483    });
 4484
 4485    let editor = cx.add_window(|window, cx| {
 4486        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4487        build_editor(buffer, window, cx)
 4488    });
 4489    _ = editor.update(cx, |editor, window, cx| {
 4490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4491            s.select_display_ranges([
 4492                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4493            ])
 4494        });
 4495        editor.delete_line(&DeleteLine, window, cx);
 4496        assert_eq!(editor.display_text(cx), "ghi\n");
 4497        assert_eq!(
 4498            display_ranges(editor, cx),
 4499            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4500        );
 4501    });
 4502
 4503    let editor = cx.add_window(|window, cx| {
 4504        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4505        build_editor(buffer, window, cx)
 4506    });
 4507    _ = editor.update(cx, |editor, window, cx| {
 4508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4509            s.select_display_ranges([
 4510                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4511            ])
 4512        });
 4513        editor.delete_line(&DeleteLine, window, cx);
 4514        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4515        assert_eq!(
 4516            display_ranges(editor, cx),
 4517            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4518        );
 4519    });
 4520}
 4521
 4522#[gpui::test]
 4523fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4524    init_test(cx, |_| {});
 4525
 4526    cx.add_window(|window, cx| {
 4527        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4528        let mut editor = build_editor(buffer.clone(), window, cx);
 4529        let buffer = buffer.read(cx).as_singleton().unwrap();
 4530
 4531        assert_eq!(
 4532            editor
 4533                .selections
 4534                .ranges::<Point>(&editor.display_snapshot(cx)),
 4535            &[Point::new(0, 0)..Point::new(0, 0)]
 4536        );
 4537
 4538        // When on single line, replace newline at end by space
 4539        editor.join_lines(&JoinLines, window, cx);
 4540        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4541        assert_eq!(
 4542            editor
 4543                .selections
 4544                .ranges::<Point>(&editor.display_snapshot(cx)),
 4545            &[Point::new(0, 3)..Point::new(0, 3)]
 4546        );
 4547
 4548        // When multiple lines are selected, remove newlines that are spanned by the selection
 4549        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4550            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4551        });
 4552        editor.join_lines(&JoinLines, window, cx);
 4553        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4554        assert_eq!(
 4555            editor
 4556                .selections
 4557                .ranges::<Point>(&editor.display_snapshot(cx)),
 4558            &[Point::new(0, 11)..Point::new(0, 11)]
 4559        );
 4560
 4561        // Undo should be transactional
 4562        editor.undo(&Undo, window, cx);
 4563        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4564        assert_eq!(
 4565            editor
 4566                .selections
 4567                .ranges::<Point>(&editor.display_snapshot(cx)),
 4568            &[Point::new(0, 5)..Point::new(2, 2)]
 4569        );
 4570
 4571        // When joining an empty line don't insert a space
 4572        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4573            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4574        });
 4575        editor.join_lines(&JoinLines, window, cx);
 4576        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4577        assert_eq!(
 4578            editor
 4579                .selections
 4580                .ranges::<Point>(&editor.display_snapshot(cx)),
 4581            [Point::new(2, 3)..Point::new(2, 3)]
 4582        );
 4583
 4584        // We can remove trailing newlines
 4585        editor.join_lines(&JoinLines, window, cx);
 4586        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4587        assert_eq!(
 4588            editor
 4589                .selections
 4590                .ranges::<Point>(&editor.display_snapshot(cx)),
 4591            [Point::new(2, 3)..Point::new(2, 3)]
 4592        );
 4593
 4594        // We don't blow up on the last line
 4595        editor.join_lines(&JoinLines, window, cx);
 4596        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4597        assert_eq!(
 4598            editor
 4599                .selections
 4600                .ranges::<Point>(&editor.display_snapshot(cx)),
 4601            [Point::new(2, 3)..Point::new(2, 3)]
 4602        );
 4603
 4604        // reset to test indentation
 4605        editor.buffer.update(cx, |buffer, cx| {
 4606            buffer.edit(
 4607                [
 4608                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4609                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4610                ],
 4611                None,
 4612                cx,
 4613            )
 4614        });
 4615
 4616        // We remove any leading spaces
 4617        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4618        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4619            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4620        });
 4621        editor.join_lines(&JoinLines, window, cx);
 4622        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4623
 4624        // We don't insert a space for a line containing only spaces
 4625        editor.join_lines(&JoinLines, window, cx);
 4626        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4627
 4628        // We ignore any leading tabs
 4629        editor.join_lines(&JoinLines, window, cx);
 4630        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4631
 4632        editor
 4633    });
 4634}
 4635
 4636#[gpui::test]
 4637fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4638    init_test(cx, |_| {});
 4639
 4640    cx.add_window(|window, cx| {
 4641        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4642        let mut editor = build_editor(buffer.clone(), window, cx);
 4643        let buffer = buffer.read(cx).as_singleton().unwrap();
 4644
 4645        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4646            s.select_ranges([
 4647                Point::new(0, 2)..Point::new(1, 1),
 4648                Point::new(1, 2)..Point::new(1, 2),
 4649                Point::new(3, 1)..Point::new(3, 2),
 4650            ])
 4651        });
 4652
 4653        editor.join_lines(&JoinLines, window, cx);
 4654        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4655
 4656        assert_eq!(
 4657            editor
 4658                .selections
 4659                .ranges::<Point>(&editor.display_snapshot(cx)),
 4660            [
 4661                Point::new(0, 7)..Point::new(0, 7),
 4662                Point::new(1, 3)..Point::new(1, 3)
 4663            ]
 4664        );
 4665        editor
 4666    });
 4667}
 4668
 4669#[gpui::test]
 4670async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4671    init_test(cx, |_| {});
 4672
 4673    let mut cx = EditorTestContext::new(cx).await;
 4674
 4675    let diff_base = r#"
 4676        Line 0
 4677        Line 1
 4678        Line 2
 4679        Line 3
 4680        "#
 4681    .unindent();
 4682
 4683    cx.set_state(
 4684        &r#"
 4685        ˇLine 0
 4686        Line 1
 4687        Line 2
 4688        Line 3
 4689        "#
 4690        .unindent(),
 4691    );
 4692
 4693    cx.set_head_text(&diff_base);
 4694    executor.run_until_parked();
 4695
 4696    // Join lines
 4697    cx.update_editor(|editor, window, cx| {
 4698        editor.join_lines(&JoinLines, window, cx);
 4699    });
 4700    executor.run_until_parked();
 4701
 4702    cx.assert_editor_state(
 4703        &r#"
 4704        Line 0ˇ Line 1
 4705        Line 2
 4706        Line 3
 4707        "#
 4708        .unindent(),
 4709    );
 4710    // Join again
 4711    cx.update_editor(|editor, window, cx| {
 4712        editor.join_lines(&JoinLines, window, cx);
 4713    });
 4714    executor.run_until_parked();
 4715
 4716    cx.assert_editor_state(
 4717        &r#"
 4718        Line 0 Line 1ˇ Line 2
 4719        Line 3
 4720        "#
 4721        .unindent(),
 4722    );
 4723}
 4724
 4725#[gpui::test]
 4726async fn test_custom_newlines_cause_no_false_positive_diffs(
 4727    executor: BackgroundExecutor,
 4728    cx: &mut TestAppContext,
 4729) {
 4730    init_test(cx, |_| {});
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4733    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4734    executor.run_until_parked();
 4735
 4736    cx.update_editor(|editor, window, cx| {
 4737        let snapshot = editor.snapshot(window, cx);
 4738        assert_eq!(
 4739            snapshot
 4740                .buffer_snapshot()
 4741                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4742                .collect::<Vec<_>>(),
 4743            Vec::new(),
 4744            "Should not have any diffs for files with custom newlines"
 4745        );
 4746    });
 4747}
 4748
 4749#[gpui::test]
 4750async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4751    init_test(cx, |_| {});
 4752
 4753    let mut cx = EditorTestContext::new(cx).await;
 4754
 4755    // Test sort_lines_case_insensitive()
 4756    cx.set_state(indoc! {"
 4757        «z
 4758        y
 4759        x
 4760        Z
 4761        Y
 4762        Xˇ»
 4763    "});
 4764    cx.update_editor(|e, window, cx| {
 4765        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4766    });
 4767    cx.assert_editor_state(indoc! {"
 4768        «x
 4769        X
 4770        y
 4771        Y
 4772        z
 4773        Zˇ»
 4774    "});
 4775
 4776    // Test sort_lines_by_length()
 4777    //
 4778    // Demonstrates:
 4779    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4780    // - sort is stable
 4781    cx.set_state(indoc! {"
 4782        «123
 4783        æ
 4784        12
 4785 4786        1
 4787        æˇ»
 4788    "});
 4789    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4790    cx.assert_editor_state(indoc! {"
 4791        «æ
 4792 4793        1
 4794        æ
 4795        12
 4796        123ˇ»
 4797    "});
 4798
 4799    // Test reverse_lines()
 4800    cx.set_state(indoc! {"
 4801        «5
 4802        4
 4803        3
 4804        2
 4805        1ˇ»
 4806    "});
 4807    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4808    cx.assert_editor_state(indoc! {"
 4809        «1
 4810        2
 4811        3
 4812        4
 4813        5ˇ»
 4814    "});
 4815
 4816    // Skip testing shuffle_line()
 4817
 4818    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4819    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4820
 4821    // Don't manipulate when cursor is on single line, but expand the selection
 4822    cx.set_state(indoc! {"
 4823        ddˇdd
 4824        ccc
 4825        bb
 4826        a
 4827    "});
 4828    cx.update_editor(|e, window, cx| {
 4829        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4830    });
 4831    cx.assert_editor_state(indoc! {"
 4832        «ddddˇ»
 4833        ccc
 4834        bb
 4835        a
 4836    "});
 4837
 4838    // Basic manipulate case
 4839    // Start selection moves to column 0
 4840    // End of selection shrinks to fit shorter line
 4841    cx.set_state(indoc! {"
 4842        dd«d
 4843        ccc
 4844        bb
 4845        aaaaaˇ»
 4846    "});
 4847    cx.update_editor(|e, window, cx| {
 4848        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4849    });
 4850    cx.assert_editor_state(indoc! {"
 4851        «aaaaa
 4852        bb
 4853        ccc
 4854        dddˇ»
 4855    "});
 4856
 4857    // Manipulate case with newlines
 4858    cx.set_state(indoc! {"
 4859        dd«d
 4860        ccc
 4861
 4862        bb
 4863        aaaaa
 4864
 4865        ˇ»
 4866    "});
 4867    cx.update_editor(|e, window, cx| {
 4868        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4869    });
 4870    cx.assert_editor_state(indoc! {"
 4871        «
 4872
 4873        aaaaa
 4874        bb
 4875        ccc
 4876        dddˇ»
 4877
 4878    "});
 4879
 4880    // Adding new line
 4881    cx.set_state(indoc! {"
 4882        aa«a
 4883        bbˇ»b
 4884    "});
 4885    cx.update_editor(|e, window, cx| {
 4886        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4887    });
 4888    cx.assert_editor_state(indoc! {"
 4889        «aaa
 4890        bbb
 4891        added_lineˇ»
 4892    "});
 4893
 4894    // Removing line
 4895    cx.set_state(indoc! {"
 4896        aa«a
 4897        bbbˇ»
 4898    "});
 4899    cx.update_editor(|e, window, cx| {
 4900        e.manipulate_immutable_lines(window, cx, |lines| {
 4901            lines.pop();
 4902        })
 4903    });
 4904    cx.assert_editor_state(indoc! {"
 4905        «aaaˇ»
 4906    "});
 4907
 4908    // Removing all lines
 4909    cx.set_state(indoc! {"
 4910        aa«a
 4911        bbbˇ»
 4912    "});
 4913    cx.update_editor(|e, window, cx| {
 4914        e.manipulate_immutable_lines(window, cx, |lines| {
 4915            lines.drain(..);
 4916        })
 4917    });
 4918    cx.assert_editor_state(indoc! {"
 4919        ˇ
 4920    "});
 4921}
 4922
 4923#[gpui::test]
 4924async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4925    init_test(cx, |_| {});
 4926
 4927    let mut cx = EditorTestContext::new(cx).await;
 4928
 4929    // Consider continuous selection as single selection
 4930    cx.set_state(indoc! {"
 4931        Aaa«aa
 4932        cˇ»c«c
 4933        bb
 4934        aaaˇ»aa
 4935    "});
 4936    cx.update_editor(|e, window, cx| {
 4937        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4938    });
 4939    cx.assert_editor_state(indoc! {"
 4940        «Aaaaa
 4941        ccc
 4942        bb
 4943        aaaaaˇ»
 4944    "});
 4945
 4946    cx.set_state(indoc! {"
 4947        Aaa«aa
 4948        cˇ»c«c
 4949        bb
 4950        aaaˇ»aa
 4951    "});
 4952    cx.update_editor(|e, window, cx| {
 4953        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4954    });
 4955    cx.assert_editor_state(indoc! {"
 4956        «Aaaaa
 4957        ccc
 4958        bbˇ»
 4959    "});
 4960
 4961    // Consider non continuous selection as distinct dedup operations
 4962    cx.set_state(indoc! {"
 4963        «aaaaa
 4964        bb
 4965        aaaaa
 4966        aaaaaˇ»
 4967
 4968        aaa«aaˇ»
 4969    "});
 4970    cx.update_editor(|e, window, cx| {
 4971        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4972    });
 4973    cx.assert_editor_state(indoc! {"
 4974        «aaaaa
 4975        bbˇ»
 4976
 4977        «aaaaaˇ»
 4978    "});
 4979}
 4980
 4981#[gpui::test]
 4982async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4983    init_test(cx, |_| {});
 4984
 4985    let mut cx = EditorTestContext::new(cx).await;
 4986
 4987    cx.set_state(indoc! {"
 4988        «Aaa
 4989        aAa
 4990        Aaaˇ»
 4991    "});
 4992    cx.update_editor(|e, window, cx| {
 4993        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4994    });
 4995    cx.assert_editor_state(indoc! {"
 4996        «Aaa
 4997        aAaˇ»
 4998    "});
 4999
 5000    cx.set_state(indoc! {"
 5001        «Aaa
 5002        aAa
 5003        aaAˇ»
 5004    "});
 5005    cx.update_editor(|e, window, cx| {
 5006        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5007    });
 5008    cx.assert_editor_state(indoc! {"
 5009        «Aaaˇ»
 5010    "});
 5011}
 5012
 5013#[gpui::test]
 5014async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5015    init_test(cx, |_| {});
 5016
 5017    let mut cx = EditorTestContext::new(cx).await;
 5018
 5019    let js_language = Arc::new(Language::new(
 5020        LanguageConfig {
 5021            name: "JavaScript".into(),
 5022            wrap_characters: Some(language::WrapCharactersConfig {
 5023                start_prefix: "<".into(),
 5024                start_suffix: ">".into(),
 5025                end_prefix: "</".into(),
 5026                end_suffix: ">".into(),
 5027            }),
 5028            ..LanguageConfig::default()
 5029        },
 5030        None,
 5031    ));
 5032
 5033    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5034
 5035    cx.set_state(indoc! {"
 5036        «testˇ»
 5037    "});
 5038    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5039    cx.assert_editor_state(indoc! {"
 5040        <«ˇ»>test</«ˇ»>
 5041    "});
 5042
 5043    cx.set_state(indoc! {"
 5044        «test
 5045         testˇ»
 5046    "});
 5047    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5048    cx.assert_editor_state(indoc! {"
 5049        <«ˇ»>test
 5050         test</«ˇ»>
 5051    "});
 5052
 5053    cx.set_state(indoc! {"
 5054        teˇst
 5055    "});
 5056    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5057    cx.assert_editor_state(indoc! {"
 5058        te<«ˇ»></«ˇ»>st
 5059    "});
 5060}
 5061
 5062#[gpui::test]
 5063async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5064    init_test(cx, |_| {});
 5065
 5066    let mut cx = EditorTestContext::new(cx).await;
 5067
 5068    let js_language = Arc::new(Language::new(
 5069        LanguageConfig {
 5070            name: "JavaScript".into(),
 5071            wrap_characters: Some(language::WrapCharactersConfig {
 5072                start_prefix: "<".into(),
 5073                start_suffix: ">".into(),
 5074                end_prefix: "</".into(),
 5075                end_suffix: ">".into(),
 5076            }),
 5077            ..LanguageConfig::default()
 5078        },
 5079        None,
 5080    ));
 5081
 5082    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5083
 5084    cx.set_state(indoc! {"
 5085        «testˇ»
 5086        «testˇ» «testˇ»
 5087        «testˇ»
 5088    "});
 5089    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5090    cx.assert_editor_state(indoc! {"
 5091        <«ˇ»>test</«ˇ»>
 5092        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5093        <«ˇ»>test</«ˇ»>
 5094    "});
 5095
 5096    cx.set_state(indoc! {"
 5097        «test
 5098         testˇ»
 5099        «test
 5100         testˇ»
 5101    "});
 5102    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5103    cx.assert_editor_state(indoc! {"
 5104        <«ˇ»>test
 5105         test</«ˇ»>
 5106        <«ˇ»>test
 5107         test</«ˇ»>
 5108    "});
 5109}
 5110
 5111#[gpui::test]
 5112async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5113    init_test(cx, |_| {});
 5114
 5115    let mut cx = EditorTestContext::new(cx).await;
 5116
 5117    let plaintext_language = Arc::new(Language::new(
 5118        LanguageConfig {
 5119            name: "Plain Text".into(),
 5120            ..LanguageConfig::default()
 5121        },
 5122        None,
 5123    ));
 5124
 5125    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5126
 5127    cx.set_state(indoc! {"
 5128        «testˇ»
 5129    "});
 5130    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5131    cx.assert_editor_state(indoc! {"
 5132      «testˇ»
 5133    "});
 5134}
 5135
 5136#[gpui::test]
 5137async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5138    init_test(cx, |_| {});
 5139
 5140    let mut cx = EditorTestContext::new(cx).await;
 5141
 5142    // Manipulate with multiple selections on a single line
 5143    cx.set_state(indoc! {"
 5144        dd«dd
 5145        cˇ»c«c
 5146        bb
 5147        aaaˇ»aa
 5148    "});
 5149    cx.update_editor(|e, window, cx| {
 5150        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5151    });
 5152    cx.assert_editor_state(indoc! {"
 5153        «aaaaa
 5154        bb
 5155        ccc
 5156        ddddˇ»
 5157    "});
 5158
 5159    // Manipulate with multiple disjoin selections
 5160    cx.set_state(indoc! {"
 5161 5162        4
 5163        3
 5164        2
 5165        1ˇ»
 5166
 5167        dd«dd
 5168        ccc
 5169        bb
 5170        aaaˇ»aa
 5171    "});
 5172    cx.update_editor(|e, window, cx| {
 5173        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5174    });
 5175    cx.assert_editor_state(indoc! {"
 5176        «1
 5177        2
 5178        3
 5179        4
 5180        5ˇ»
 5181
 5182        «aaaaa
 5183        bb
 5184        ccc
 5185        ddddˇ»
 5186    "});
 5187
 5188    // Adding lines on each selection
 5189    cx.set_state(indoc! {"
 5190 5191        1ˇ»
 5192
 5193        bb«bb
 5194        aaaˇ»aa
 5195    "});
 5196    cx.update_editor(|e, window, cx| {
 5197        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5198    });
 5199    cx.assert_editor_state(indoc! {"
 5200        «2
 5201        1
 5202        added lineˇ»
 5203
 5204        «bbbb
 5205        aaaaa
 5206        added lineˇ»
 5207    "});
 5208
 5209    // Removing lines on each selection
 5210    cx.set_state(indoc! {"
 5211 5212        1ˇ»
 5213
 5214        bb«bb
 5215        aaaˇ»aa
 5216    "});
 5217    cx.update_editor(|e, window, cx| {
 5218        e.manipulate_immutable_lines(window, cx, |lines| {
 5219            lines.pop();
 5220        })
 5221    });
 5222    cx.assert_editor_state(indoc! {"
 5223        «2ˇ»
 5224
 5225        «bbbbˇ»
 5226    "});
 5227}
 5228
 5229#[gpui::test]
 5230async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5231    init_test(cx, |settings| {
 5232        settings.defaults.tab_size = NonZeroU32::new(3)
 5233    });
 5234
 5235    let mut cx = EditorTestContext::new(cx).await;
 5236
 5237    // MULTI SELECTION
 5238    // Ln.1 "«" tests empty lines
 5239    // Ln.9 tests just leading whitespace
 5240    cx.set_state(indoc! {"
 5241        «
 5242        abc                 // No indentationˇ»
 5243        «\tabc              // 1 tabˇ»
 5244        \t\tabc «      ˇ»   // 2 tabs
 5245        \t ab«c             // Tab followed by space
 5246         \tabc              // Space followed by tab (3 spaces should be the result)
 5247        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5248           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5249        \t
 5250        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5251    "});
 5252    cx.update_editor(|e, window, cx| {
 5253        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5254    });
 5255    cx.assert_editor_state(
 5256        indoc! {"
 5257            «
 5258            abc                 // No indentation
 5259               abc              // 1 tab
 5260                  abc          // 2 tabs
 5261                abc             // Tab followed by space
 5262               abc              // Space followed by tab (3 spaces should be the result)
 5263                           abc   // Mixed indentation (tab conversion depends on the column)
 5264               abc         // Already space indented
 5265               ·
 5266               abc\tdef          // Only the leading tab is manipulatedˇ»
 5267        "}
 5268        .replace("·", "")
 5269        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5270    );
 5271
 5272    // Test on just a few lines, the others should remain unchanged
 5273    // Only lines (3, 5, 10, 11) should change
 5274    cx.set_state(
 5275        indoc! {"
 5276            ·
 5277            abc                 // No indentation
 5278            \tabcˇ               // 1 tab
 5279            \t\tabc             // 2 tabs
 5280            \t abcˇ              // Tab followed by space
 5281             \tabc              // Space followed by tab (3 spaces should be the result)
 5282            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5283               abc              // Already space indented
 5284            «\t
 5285            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5286        "}
 5287        .replace("·", "")
 5288        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5289    );
 5290    cx.update_editor(|e, window, cx| {
 5291        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5292    });
 5293    cx.assert_editor_state(
 5294        indoc! {"
 5295            ·
 5296            abc                 // No indentation
 5297            «   abc               // 1 tabˇ»
 5298            \t\tabc             // 2 tabs
 5299            «    abc              // Tab followed by spaceˇ»
 5300             \tabc              // Space followed by tab (3 spaces should be the result)
 5301            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5302               abc              // Already space indented
 5303            «   ·
 5304               abc\tdef          // Only the leading tab is manipulatedˇ»
 5305        "}
 5306        .replace("·", "")
 5307        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5308    );
 5309
 5310    // SINGLE SELECTION
 5311    // Ln.1 "«" tests empty lines
 5312    // Ln.9 tests just leading whitespace
 5313    cx.set_state(indoc! {"
 5314        «
 5315        abc                 // No indentation
 5316        \tabc               // 1 tab
 5317        \t\tabc             // 2 tabs
 5318        \t abc              // Tab followed by space
 5319         \tabc              // Space followed by tab (3 spaces should be the result)
 5320        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5321           abc              // Already space indented
 5322        \t
 5323        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5324    "});
 5325    cx.update_editor(|e, window, cx| {
 5326        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5327    });
 5328    cx.assert_editor_state(
 5329        indoc! {"
 5330            «
 5331            abc                 // No indentation
 5332               abc               // 1 tab
 5333                  abc             // 2 tabs
 5334                abc              // Tab followed by space
 5335               abc              // Space followed by tab (3 spaces should be the result)
 5336                           abc   // Mixed indentation (tab conversion depends on the column)
 5337               abc              // Already space indented
 5338               ·
 5339               abc\tdef          // Only the leading tab is manipulatedˇ»
 5340        "}
 5341        .replace("·", "")
 5342        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5343    );
 5344}
 5345
 5346#[gpui::test]
 5347async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5348    init_test(cx, |settings| {
 5349        settings.defaults.tab_size = NonZeroU32::new(3)
 5350    });
 5351
 5352    let mut cx = EditorTestContext::new(cx).await;
 5353
 5354    // MULTI SELECTION
 5355    // Ln.1 "«" tests empty lines
 5356    // Ln.11 tests just leading whitespace
 5357    cx.set_state(indoc! {"
 5358        «
 5359        abˇ»ˇc                 // No indentation
 5360         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5361          abc  «             // 2 spaces (< 3 so dont convert)
 5362           abc              // 3 spaces (convert)
 5363             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5364        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5365        «\t abc              // Tab followed by space
 5366         \tabc              // Space followed by tab (should be consumed due to tab)
 5367        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5368           \tˇ»  «\t
 5369           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5370    "});
 5371    cx.update_editor(|e, window, cx| {
 5372        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5373    });
 5374    cx.assert_editor_state(indoc! {"
 5375        «
 5376        abc                 // No indentation
 5377         abc                // 1 space (< 3 so dont convert)
 5378          abc               // 2 spaces (< 3 so dont convert)
 5379        \tabc              // 3 spaces (convert)
 5380        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5381        \t\t\tabc           // Already tab indented
 5382        \t abc              // Tab followed by space
 5383        \tabc              // Space followed by tab (should be consumed due to tab)
 5384        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5385        \t\t\t
 5386        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5387    "});
 5388
 5389    // Test on just a few lines, the other should remain unchanged
 5390    // Only lines (4, 8, 11, 12) should change
 5391    cx.set_state(
 5392        indoc! {"
 5393            ·
 5394            abc                 // No indentation
 5395             abc                // 1 space (< 3 so dont convert)
 5396              abc               // 2 spaces (< 3 so dont convert)
 5397            «   abc              // 3 spaces (convert)ˇ»
 5398                 abc            // 5 spaces (1 tab + 2 spaces)
 5399            \t\t\tabc           // Already tab indented
 5400            \t abc              // Tab followed by space
 5401             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5402               \t\t  \tabc      // Mixed indentation
 5403            \t \t  \t   \tabc   // Mixed indentation
 5404               \t  \tˇ
 5405            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5406        "}
 5407        .replace("·", "")
 5408        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5409    );
 5410    cx.update_editor(|e, window, cx| {
 5411        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5412    });
 5413    cx.assert_editor_state(
 5414        indoc! {"
 5415            ·
 5416            abc                 // No indentation
 5417             abc                // 1 space (< 3 so dont convert)
 5418              abc               // 2 spaces (< 3 so dont convert)
 5419            «\tabc              // 3 spaces (convert)ˇ»
 5420                 abc            // 5 spaces (1 tab + 2 spaces)
 5421            \t\t\tabc           // Already tab indented
 5422            \t abc              // Tab followed by space
 5423            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5424               \t\t  \tabc      // Mixed indentation
 5425            \t \t  \t   \tabc   // Mixed indentation
 5426            «\t\t\t
 5427            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5428        "}
 5429        .replace("·", "")
 5430        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5431    );
 5432
 5433    // SINGLE SELECTION
 5434    // Ln.1 "«" tests empty lines
 5435    // Ln.11 tests just leading whitespace
 5436    cx.set_state(indoc! {"
 5437        «
 5438        abc                 // No indentation
 5439         abc                // 1 space (< 3 so dont convert)
 5440          abc               // 2 spaces (< 3 so dont convert)
 5441           abc              // 3 spaces (convert)
 5442             abc            // 5 spaces (1 tab + 2 spaces)
 5443        \t\t\tabc           // Already tab indented
 5444        \t abc              // Tab followed by space
 5445         \tabc              // Space followed by tab (should be consumed due to tab)
 5446        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5447           \t  \t
 5448           abc   \t         // Only the leading spaces should be convertedˇ»
 5449    "});
 5450    cx.update_editor(|e, window, cx| {
 5451        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5452    });
 5453    cx.assert_editor_state(indoc! {"
 5454        «
 5455        abc                 // No indentation
 5456         abc                // 1 space (< 3 so dont convert)
 5457          abc               // 2 spaces (< 3 so dont convert)
 5458        \tabc              // 3 spaces (convert)
 5459        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5460        \t\t\tabc           // Already tab indented
 5461        \t abc              // Tab followed by space
 5462        \tabc              // Space followed by tab (should be consumed due to tab)
 5463        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5464        \t\t\t
 5465        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5466    "});
 5467}
 5468
 5469#[gpui::test]
 5470async fn test_toggle_case(cx: &mut TestAppContext) {
 5471    init_test(cx, |_| {});
 5472
 5473    let mut cx = EditorTestContext::new(cx).await;
 5474
 5475    // If all lower case -> upper case
 5476    cx.set_state(indoc! {"
 5477        «hello worldˇ»
 5478    "});
 5479    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5480    cx.assert_editor_state(indoc! {"
 5481        «HELLO WORLDˇ»
 5482    "});
 5483
 5484    // If all upper case -> lower case
 5485    cx.set_state(indoc! {"
 5486        «HELLO WORLDˇ»
 5487    "});
 5488    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5489    cx.assert_editor_state(indoc! {"
 5490        «hello worldˇ»
 5491    "});
 5492
 5493    // If any upper case characters are identified -> lower case
 5494    // This matches JetBrains IDEs
 5495    cx.set_state(indoc! {"
 5496        «hEllo worldˇ»
 5497    "});
 5498    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5499    cx.assert_editor_state(indoc! {"
 5500        «hello worldˇ»
 5501    "});
 5502}
 5503
 5504#[gpui::test]
 5505async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5506    init_test(cx, |_| {});
 5507
 5508    let mut cx = EditorTestContext::new(cx).await;
 5509
 5510    cx.set_state(indoc! {"
 5511        «implement-windows-supportˇ»
 5512    "});
 5513    cx.update_editor(|e, window, cx| {
 5514        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5515    });
 5516    cx.assert_editor_state(indoc! {"
 5517        «Implement windows supportˇ»
 5518    "});
 5519}
 5520
 5521#[gpui::test]
 5522async fn test_manipulate_text(cx: &mut TestAppContext) {
 5523    init_test(cx, |_| {});
 5524
 5525    let mut cx = EditorTestContext::new(cx).await;
 5526
 5527    // Test convert_to_upper_case()
 5528    cx.set_state(indoc! {"
 5529        «hello worldˇ»
 5530    "});
 5531    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5532    cx.assert_editor_state(indoc! {"
 5533        «HELLO WORLDˇ»
 5534    "});
 5535
 5536    // Test convert_to_lower_case()
 5537    cx.set_state(indoc! {"
 5538        «HELLO WORLDˇ»
 5539    "});
 5540    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5541    cx.assert_editor_state(indoc! {"
 5542        «hello worldˇ»
 5543    "});
 5544
 5545    // Test multiple line, single selection case
 5546    cx.set_state(indoc! {"
 5547        «The quick brown
 5548        fox jumps over
 5549        the lazy dogˇ»
 5550    "});
 5551    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5552    cx.assert_editor_state(indoc! {"
 5553        «The Quick Brown
 5554        Fox Jumps Over
 5555        The Lazy Dogˇ»
 5556    "});
 5557
 5558    // Test multiple line, single selection case
 5559    cx.set_state(indoc! {"
 5560        «The quick brown
 5561        fox jumps over
 5562        the lazy dogˇ»
 5563    "});
 5564    cx.update_editor(|e, window, cx| {
 5565        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5566    });
 5567    cx.assert_editor_state(indoc! {"
 5568        «TheQuickBrown
 5569        FoxJumpsOver
 5570        TheLazyDogˇ»
 5571    "});
 5572
 5573    // From here on out, test more complex cases of manipulate_text()
 5574
 5575    // Test no selection case - should affect words cursors are in
 5576    // Cursor at beginning, middle, and end of word
 5577    cx.set_state(indoc! {"
 5578        ˇhello big beauˇtiful worldˇ
 5579    "});
 5580    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5581    cx.assert_editor_state(indoc! {"
 5582        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5583    "});
 5584
 5585    // Test multiple selections on a single line and across multiple lines
 5586    cx.set_state(indoc! {"
 5587        «Theˇ» quick «brown
 5588        foxˇ» jumps «overˇ»
 5589        the «lazyˇ» dog
 5590    "});
 5591    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5592    cx.assert_editor_state(indoc! {"
 5593        «THEˇ» quick «BROWN
 5594        FOXˇ» jumps «OVERˇ»
 5595        the «LAZYˇ» dog
 5596    "});
 5597
 5598    // Test case where text length grows
 5599    cx.set_state(indoc! {"
 5600        «tschüߡ»
 5601    "});
 5602    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5603    cx.assert_editor_state(indoc! {"
 5604        «TSCHÜSSˇ»
 5605    "});
 5606
 5607    // Test to make sure we don't crash when text shrinks
 5608    cx.set_state(indoc! {"
 5609        aaa_bbbˇ
 5610    "});
 5611    cx.update_editor(|e, window, cx| {
 5612        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5613    });
 5614    cx.assert_editor_state(indoc! {"
 5615        «aaaBbbˇ»
 5616    "});
 5617
 5618    // Test to make sure we all aware of the fact that each word can grow and shrink
 5619    // Final selections should be aware of this fact
 5620    cx.set_state(indoc! {"
 5621        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5622    "});
 5623    cx.update_editor(|e, window, cx| {
 5624        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5625    });
 5626    cx.assert_editor_state(indoc! {"
 5627        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5628    "});
 5629
 5630    cx.set_state(indoc! {"
 5631        «hElLo, WoRld!ˇ»
 5632    "});
 5633    cx.update_editor(|e, window, cx| {
 5634        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5635    });
 5636    cx.assert_editor_state(indoc! {"
 5637        «HeLlO, wOrLD!ˇ»
 5638    "});
 5639
 5640    // Test selections with `line_mode() = true`.
 5641    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5642    cx.set_state(indoc! {"
 5643        «The quick brown
 5644        fox jumps over
 5645        tˇ»he lazy dog
 5646    "});
 5647    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5648    cx.assert_editor_state(indoc! {"
 5649        «THE QUICK BROWN
 5650        FOX JUMPS OVER
 5651        THE LAZY DOGˇ»
 5652    "});
 5653}
 5654
 5655#[gpui::test]
 5656fn test_duplicate_line(cx: &mut TestAppContext) {
 5657    init_test(cx, |_| {});
 5658
 5659    let editor = cx.add_window(|window, cx| {
 5660        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5661        build_editor(buffer, window, cx)
 5662    });
 5663    _ = editor.update(cx, |editor, window, cx| {
 5664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5665            s.select_display_ranges([
 5666                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5667                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5668                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5669                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5670            ])
 5671        });
 5672        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5673        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5674        assert_eq!(
 5675            display_ranges(editor, cx),
 5676            vec![
 5677                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5678                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5679                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5680                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5681            ]
 5682        );
 5683    });
 5684
 5685    let editor = cx.add_window(|window, cx| {
 5686        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5687        build_editor(buffer, window, cx)
 5688    });
 5689    _ = editor.update(cx, |editor, window, cx| {
 5690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5691            s.select_display_ranges([
 5692                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5693                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5694            ])
 5695        });
 5696        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5697        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5698        assert_eq!(
 5699            display_ranges(editor, cx),
 5700            vec![
 5701                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5702                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5703            ]
 5704        );
 5705    });
 5706
 5707    // With `duplicate_line_up` the selections move to the duplicated lines,
 5708    // which are inserted above the original lines
 5709    let editor = cx.add_window(|window, cx| {
 5710        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5711        build_editor(buffer, window, cx)
 5712    });
 5713    _ = editor.update(cx, |editor, window, cx| {
 5714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5715            s.select_display_ranges([
 5716                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5717                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5718                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5719                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5720            ])
 5721        });
 5722        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5723        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5724        assert_eq!(
 5725            display_ranges(editor, cx),
 5726            vec![
 5727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5728                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5729                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5730                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5731            ]
 5732        );
 5733    });
 5734
 5735    let editor = cx.add_window(|window, cx| {
 5736        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5737        build_editor(buffer, window, cx)
 5738    });
 5739    _ = editor.update(cx, |editor, window, cx| {
 5740        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5741            s.select_display_ranges([
 5742                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5743                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5744            ])
 5745        });
 5746        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5747        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5748        assert_eq!(
 5749            display_ranges(editor, cx),
 5750            vec![
 5751                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5752                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5753            ]
 5754        );
 5755    });
 5756
 5757    let editor = cx.add_window(|window, cx| {
 5758        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5759        build_editor(buffer, window, cx)
 5760    });
 5761    _ = editor.update(cx, |editor, window, cx| {
 5762        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5763            s.select_display_ranges([
 5764                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5765                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5766            ])
 5767        });
 5768        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5769        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5770        assert_eq!(
 5771            display_ranges(editor, cx),
 5772            vec![
 5773                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5774                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5775            ]
 5776        );
 5777    });
 5778}
 5779
 5780#[gpui::test]
 5781fn test_move_line_up_down(cx: &mut TestAppContext) {
 5782    init_test(cx, |_| {});
 5783
 5784    let editor = cx.add_window(|window, cx| {
 5785        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5786        build_editor(buffer, window, cx)
 5787    });
 5788    _ = editor.update(cx, |editor, window, cx| {
 5789        editor.fold_creases(
 5790            vec![
 5791                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5792                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5793                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5794            ],
 5795            true,
 5796            window,
 5797            cx,
 5798        );
 5799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5800            s.select_display_ranges([
 5801                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5802                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5803                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5804                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5805            ])
 5806        });
 5807        assert_eq!(
 5808            editor.display_text(cx),
 5809            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5810        );
 5811
 5812        editor.move_line_up(&MoveLineUp, window, cx);
 5813        assert_eq!(
 5814            editor.display_text(cx),
 5815            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5816        );
 5817        assert_eq!(
 5818            display_ranges(editor, cx),
 5819            vec![
 5820                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5821                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5822                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5823                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5824            ]
 5825        );
 5826    });
 5827
 5828    _ = editor.update(cx, |editor, window, cx| {
 5829        editor.move_line_down(&MoveLineDown, window, cx);
 5830        assert_eq!(
 5831            editor.display_text(cx),
 5832            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5833        );
 5834        assert_eq!(
 5835            display_ranges(editor, cx),
 5836            vec![
 5837                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5838                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5839                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5840                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5841            ]
 5842        );
 5843    });
 5844
 5845    _ = editor.update(cx, |editor, window, cx| {
 5846        editor.move_line_down(&MoveLineDown, window, cx);
 5847        assert_eq!(
 5848            editor.display_text(cx),
 5849            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5850        );
 5851        assert_eq!(
 5852            display_ranges(editor, cx),
 5853            vec![
 5854                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5855                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5856                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5857                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5858            ]
 5859        );
 5860    });
 5861
 5862    _ = editor.update(cx, |editor, window, cx| {
 5863        editor.move_line_up(&MoveLineUp, window, cx);
 5864        assert_eq!(
 5865            editor.display_text(cx),
 5866            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5867        );
 5868        assert_eq!(
 5869            display_ranges(editor, cx),
 5870            vec![
 5871                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5872                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5873                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5874                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5875            ]
 5876        );
 5877    });
 5878}
 5879
 5880#[gpui::test]
 5881fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5882    init_test(cx, |_| {});
 5883    let editor = cx.add_window(|window, cx| {
 5884        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5885        build_editor(buffer, window, cx)
 5886    });
 5887    _ = editor.update(cx, |editor, window, cx| {
 5888        editor.fold_creases(
 5889            vec![Crease::simple(
 5890                Point::new(6, 4)..Point::new(7, 4),
 5891                FoldPlaceholder::test(),
 5892            )],
 5893            true,
 5894            window,
 5895            cx,
 5896        );
 5897        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5898            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5899        });
 5900        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5901        editor.move_line_up(&MoveLineUp, window, cx);
 5902        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5903        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5904    });
 5905}
 5906
 5907#[gpui::test]
 5908fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5909    init_test(cx, |_| {});
 5910
 5911    let editor = cx.add_window(|window, cx| {
 5912        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5913        build_editor(buffer, window, cx)
 5914    });
 5915    _ = editor.update(cx, |editor, window, cx| {
 5916        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5917        editor.insert_blocks(
 5918            [BlockProperties {
 5919                style: BlockStyle::Fixed,
 5920                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5921                height: Some(1),
 5922                render: Arc::new(|_| div().into_any()),
 5923                priority: 0,
 5924            }],
 5925            Some(Autoscroll::fit()),
 5926            cx,
 5927        );
 5928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5929            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5930        });
 5931        editor.move_line_down(&MoveLineDown, window, cx);
 5932    });
 5933}
 5934
 5935#[gpui::test]
 5936async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5937    init_test(cx, |_| {});
 5938
 5939    let mut cx = EditorTestContext::new(cx).await;
 5940    cx.set_state(
 5941        &"
 5942            ˇzero
 5943            one
 5944            two
 5945            three
 5946            four
 5947            five
 5948        "
 5949        .unindent(),
 5950    );
 5951
 5952    // Create a four-line block that replaces three lines of text.
 5953    cx.update_editor(|editor, window, cx| {
 5954        let snapshot = editor.snapshot(window, cx);
 5955        let snapshot = &snapshot.buffer_snapshot();
 5956        let placement = BlockPlacement::Replace(
 5957            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5958        );
 5959        editor.insert_blocks(
 5960            [BlockProperties {
 5961                placement,
 5962                height: Some(4),
 5963                style: BlockStyle::Sticky,
 5964                render: Arc::new(|_| gpui::div().into_any_element()),
 5965                priority: 0,
 5966            }],
 5967            None,
 5968            cx,
 5969        );
 5970    });
 5971
 5972    // Move down so that the cursor touches the block.
 5973    cx.update_editor(|editor, window, cx| {
 5974        editor.move_down(&Default::default(), window, cx);
 5975    });
 5976    cx.assert_editor_state(
 5977        &"
 5978            zero
 5979            «one
 5980            two
 5981            threeˇ»
 5982            four
 5983            five
 5984        "
 5985        .unindent(),
 5986    );
 5987
 5988    // Move down past the block.
 5989    cx.update_editor(|editor, window, cx| {
 5990        editor.move_down(&Default::default(), window, cx);
 5991    });
 5992    cx.assert_editor_state(
 5993        &"
 5994            zero
 5995            one
 5996            two
 5997            three
 5998            ˇfour
 5999            five
 6000        "
 6001        .unindent(),
 6002    );
 6003}
 6004
 6005#[gpui::test]
 6006fn test_transpose(cx: &mut TestAppContext) {
 6007    init_test(cx, |_| {});
 6008
 6009    _ = cx.add_window(|window, cx| {
 6010        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6011        editor.set_style(EditorStyle::default(), window, cx);
 6012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6013            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6014        });
 6015        editor.transpose(&Default::default(), window, cx);
 6016        assert_eq!(editor.text(cx), "bac");
 6017        assert_eq!(
 6018            editor.selections.ranges(&editor.display_snapshot(cx)),
 6019            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6020        );
 6021
 6022        editor.transpose(&Default::default(), window, cx);
 6023        assert_eq!(editor.text(cx), "bca");
 6024        assert_eq!(
 6025            editor.selections.ranges(&editor.display_snapshot(cx)),
 6026            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6027        );
 6028
 6029        editor.transpose(&Default::default(), window, cx);
 6030        assert_eq!(editor.text(cx), "bac");
 6031        assert_eq!(
 6032            editor.selections.ranges(&editor.display_snapshot(cx)),
 6033            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6034        );
 6035
 6036        editor
 6037    });
 6038
 6039    _ = cx.add_window(|window, cx| {
 6040        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6041        editor.set_style(EditorStyle::default(), window, cx);
 6042        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6043            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6044        });
 6045        editor.transpose(&Default::default(), window, cx);
 6046        assert_eq!(editor.text(cx), "acb\nde");
 6047        assert_eq!(
 6048            editor.selections.ranges(&editor.display_snapshot(cx)),
 6049            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6050        );
 6051
 6052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6053            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6054        });
 6055        editor.transpose(&Default::default(), window, cx);
 6056        assert_eq!(editor.text(cx), "acbd\ne");
 6057        assert_eq!(
 6058            editor.selections.ranges(&editor.display_snapshot(cx)),
 6059            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6060        );
 6061
 6062        editor.transpose(&Default::default(), window, cx);
 6063        assert_eq!(editor.text(cx), "acbde\n");
 6064        assert_eq!(
 6065            editor.selections.ranges(&editor.display_snapshot(cx)),
 6066            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6067        );
 6068
 6069        editor.transpose(&Default::default(), window, cx);
 6070        assert_eq!(editor.text(cx), "acbd\ne");
 6071        assert_eq!(
 6072            editor.selections.ranges(&editor.display_snapshot(cx)),
 6073            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6074        );
 6075
 6076        editor
 6077    });
 6078
 6079    _ = cx.add_window(|window, cx| {
 6080        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6081        editor.set_style(EditorStyle::default(), window, cx);
 6082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6083            s.select_ranges([
 6084                MultiBufferOffset(1)..MultiBufferOffset(1),
 6085                MultiBufferOffset(2)..MultiBufferOffset(2),
 6086                MultiBufferOffset(4)..MultiBufferOffset(4),
 6087            ])
 6088        });
 6089        editor.transpose(&Default::default(), window, cx);
 6090        assert_eq!(editor.text(cx), "bacd\ne");
 6091        assert_eq!(
 6092            editor.selections.ranges(&editor.display_snapshot(cx)),
 6093            [
 6094                MultiBufferOffset(2)..MultiBufferOffset(2),
 6095                MultiBufferOffset(3)..MultiBufferOffset(3),
 6096                MultiBufferOffset(5)..MultiBufferOffset(5)
 6097            ]
 6098        );
 6099
 6100        editor.transpose(&Default::default(), window, cx);
 6101        assert_eq!(editor.text(cx), "bcade\n");
 6102        assert_eq!(
 6103            editor.selections.ranges(&editor.display_snapshot(cx)),
 6104            [
 6105                MultiBufferOffset(3)..MultiBufferOffset(3),
 6106                MultiBufferOffset(4)..MultiBufferOffset(4),
 6107                MultiBufferOffset(6)..MultiBufferOffset(6)
 6108            ]
 6109        );
 6110
 6111        editor.transpose(&Default::default(), window, cx);
 6112        assert_eq!(editor.text(cx), "bcda\ne");
 6113        assert_eq!(
 6114            editor.selections.ranges(&editor.display_snapshot(cx)),
 6115            [
 6116                MultiBufferOffset(4)..MultiBufferOffset(4),
 6117                MultiBufferOffset(6)..MultiBufferOffset(6)
 6118            ]
 6119        );
 6120
 6121        editor.transpose(&Default::default(), window, cx);
 6122        assert_eq!(editor.text(cx), "bcade\n");
 6123        assert_eq!(
 6124            editor.selections.ranges(&editor.display_snapshot(cx)),
 6125            [
 6126                MultiBufferOffset(4)..MultiBufferOffset(4),
 6127                MultiBufferOffset(6)..MultiBufferOffset(6)
 6128            ]
 6129        );
 6130
 6131        editor.transpose(&Default::default(), window, cx);
 6132        assert_eq!(editor.text(cx), "bcaed\n");
 6133        assert_eq!(
 6134            editor.selections.ranges(&editor.display_snapshot(cx)),
 6135            [
 6136                MultiBufferOffset(5)..MultiBufferOffset(5),
 6137                MultiBufferOffset(6)..MultiBufferOffset(6)
 6138            ]
 6139        );
 6140
 6141        editor
 6142    });
 6143
 6144    _ = cx.add_window(|window, cx| {
 6145        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6146        editor.set_style(EditorStyle::default(), window, cx);
 6147        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6148            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6149        });
 6150        editor.transpose(&Default::default(), window, cx);
 6151        assert_eq!(editor.text(cx), "🏀🍐✋");
 6152        assert_eq!(
 6153            editor.selections.ranges(&editor.display_snapshot(cx)),
 6154            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6155        );
 6156
 6157        editor.transpose(&Default::default(), window, cx);
 6158        assert_eq!(editor.text(cx), "🏀✋🍐");
 6159        assert_eq!(
 6160            editor.selections.ranges(&editor.display_snapshot(cx)),
 6161            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6162        );
 6163
 6164        editor.transpose(&Default::default(), window, cx);
 6165        assert_eq!(editor.text(cx), "🏀🍐✋");
 6166        assert_eq!(
 6167            editor.selections.ranges(&editor.display_snapshot(cx)),
 6168            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6169        );
 6170
 6171        editor
 6172    });
 6173}
 6174
 6175#[gpui::test]
 6176async fn test_rewrap(cx: &mut TestAppContext) {
 6177    init_test(cx, |settings| {
 6178        settings.languages.0.extend([
 6179            (
 6180                "Markdown".into(),
 6181                LanguageSettingsContent {
 6182                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6183                    preferred_line_length: Some(40),
 6184                    ..Default::default()
 6185                },
 6186            ),
 6187            (
 6188                "Plain Text".into(),
 6189                LanguageSettingsContent {
 6190                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6191                    preferred_line_length: Some(40),
 6192                    ..Default::default()
 6193                },
 6194            ),
 6195            (
 6196                "C++".into(),
 6197                LanguageSettingsContent {
 6198                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6199                    preferred_line_length: Some(40),
 6200                    ..Default::default()
 6201                },
 6202            ),
 6203            (
 6204                "Python".into(),
 6205                LanguageSettingsContent {
 6206                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6207                    preferred_line_length: Some(40),
 6208                    ..Default::default()
 6209                },
 6210            ),
 6211            (
 6212                "Rust".into(),
 6213                LanguageSettingsContent {
 6214                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6215                    preferred_line_length: Some(40),
 6216                    ..Default::default()
 6217                },
 6218            ),
 6219        ])
 6220    });
 6221
 6222    let mut cx = EditorTestContext::new(cx).await;
 6223
 6224    let cpp_language = Arc::new(Language::new(
 6225        LanguageConfig {
 6226            name: "C++".into(),
 6227            line_comments: vec!["// ".into()],
 6228            ..LanguageConfig::default()
 6229        },
 6230        None,
 6231    ));
 6232    let python_language = Arc::new(Language::new(
 6233        LanguageConfig {
 6234            name: "Python".into(),
 6235            line_comments: vec!["# ".into()],
 6236            ..LanguageConfig::default()
 6237        },
 6238        None,
 6239    ));
 6240    let markdown_language = Arc::new(Language::new(
 6241        LanguageConfig {
 6242            name: "Markdown".into(),
 6243            rewrap_prefixes: vec![
 6244                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6245                regex::Regex::new("[-*+]\\s+").unwrap(),
 6246            ],
 6247            ..LanguageConfig::default()
 6248        },
 6249        None,
 6250    ));
 6251    let rust_language = Arc::new(
 6252        Language::new(
 6253            LanguageConfig {
 6254                name: "Rust".into(),
 6255                line_comments: vec!["// ".into(), "/// ".into()],
 6256                ..LanguageConfig::default()
 6257            },
 6258            Some(tree_sitter_rust::LANGUAGE.into()),
 6259        )
 6260        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6261        .unwrap(),
 6262    );
 6263
 6264    let plaintext_language = Arc::new(Language::new(
 6265        LanguageConfig {
 6266            name: "Plain Text".into(),
 6267            ..LanguageConfig::default()
 6268        },
 6269        None,
 6270    ));
 6271
 6272    // Test basic rewrapping of a long line with a cursor
 6273    assert_rewrap(
 6274        indoc! {"
 6275            // ˇThis is a long comment that needs to be wrapped.
 6276        "},
 6277        indoc! {"
 6278            // ˇThis is a long comment that needs to
 6279            // be wrapped.
 6280        "},
 6281        cpp_language.clone(),
 6282        &mut cx,
 6283    );
 6284
 6285    // Test rewrapping a full selection
 6286    assert_rewrap(
 6287        indoc! {"
 6288            «// This selected long comment needs to be wrapped.ˇ»"
 6289        },
 6290        indoc! {"
 6291            «// This selected long comment needs to
 6292            // be wrapped.ˇ»"
 6293        },
 6294        cpp_language.clone(),
 6295        &mut cx,
 6296    );
 6297
 6298    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6299    assert_rewrap(
 6300        indoc! {"
 6301            // ˇThis is the first line.
 6302            // Thisˇ is the second line.
 6303            // This is the thirdˇ line, all part of one paragraph.
 6304         "},
 6305        indoc! {"
 6306            // ˇThis is the first line. Thisˇ is the
 6307            // second line. This is the thirdˇ line,
 6308            // all part of one paragraph.
 6309         "},
 6310        cpp_language.clone(),
 6311        &mut cx,
 6312    );
 6313
 6314    // Test multiple cursors in different paragraphs trigger separate rewraps
 6315    assert_rewrap(
 6316        indoc! {"
 6317            // ˇThis is the first paragraph, first line.
 6318            // ˇThis is the first paragraph, second line.
 6319
 6320            // ˇThis is the second paragraph, first line.
 6321            // ˇThis is the second paragraph, second line.
 6322        "},
 6323        indoc! {"
 6324            // ˇThis is the first paragraph, first
 6325            // line. ˇThis is the first paragraph,
 6326            // second line.
 6327
 6328            // ˇThis is the second paragraph, first
 6329            // line. ˇThis is the second paragraph,
 6330            // second line.
 6331        "},
 6332        cpp_language.clone(),
 6333        &mut cx,
 6334    );
 6335
 6336    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6337    assert_rewrap(
 6338        indoc! {"
 6339            «// A regular long long comment to be wrapped.
 6340            /// A documentation long comment to be wrapped.ˇ»
 6341          "},
 6342        indoc! {"
 6343            «// A regular long long comment to be
 6344            // wrapped.
 6345            /// A documentation long comment to be
 6346            /// wrapped.ˇ»
 6347          "},
 6348        rust_language.clone(),
 6349        &mut cx,
 6350    );
 6351
 6352    // Test that change in indentation level trigger seperate rewraps
 6353    assert_rewrap(
 6354        indoc! {"
 6355            fn foo() {
 6356                «// This is a long comment at the base indent.
 6357                    // This is a long comment at the next indent.ˇ»
 6358            }
 6359        "},
 6360        indoc! {"
 6361            fn foo() {
 6362                «// This is a long comment at the
 6363                // base indent.
 6364                    // This is a long comment at the
 6365                    // next indent.ˇ»
 6366            }
 6367        "},
 6368        rust_language.clone(),
 6369        &mut cx,
 6370    );
 6371
 6372    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6373    assert_rewrap(
 6374        indoc! {"
 6375            # ˇThis is a long comment using a pound sign.
 6376        "},
 6377        indoc! {"
 6378            # ˇThis is a long comment using a pound
 6379            # sign.
 6380        "},
 6381        python_language,
 6382        &mut cx,
 6383    );
 6384
 6385    // Test rewrapping only affects comments, not code even when selected
 6386    assert_rewrap(
 6387        indoc! {"
 6388            «/// This doc comment is long and should be wrapped.
 6389            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6390        "},
 6391        indoc! {"
 6392            «/// This doc comment is long and should
 6393            /// be wrapped.
 6394            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6395        "},
 6396        rust_language.clone(),
 6397        &mut cx,
 6398    );
 6399
 6400    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6401    assert_rewrap(
 6402        indoc! {"
 6403            # Header
 6404
 6405            A long long long line of markdown text to wrap.ˇ
 6406         "},
 6407        indoc! {"
 6408            # Header
 6409
 6410            A long long long line of markdown text
 6411            to wrap.ˇ
 6412         "},
 6413        markdown_language.clone(),
 6414        &mut cx,
 6415    );
 6416
 6417    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6418    assert_rewrap(
 6419        indoc! {"
 6420            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6421            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6422            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6423        "},
 6424        indoc! {"
 6425            «1. This is a numbered list item that is
 6426               very long and needs to be wrapped
 6427               properly.
 6428            2. This is a numbered list item that is
 6429               very long and needs to be wrapped
 6430               properly.
 6431            - This is an unordered list item that is
 6432              also very long and should not merge
 6433              with the numbered item.ˇ»
 6434        "},
 6435        markdown_language.clone(),
 6436        &mut cx,
 6437    );
 6438
 6439    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6440    assert_rewrap(
 6441        indoc! {"
 6442            «1. This is a numbered list item that is
 6443            very long and needs to be wrapped
 6444            properly.
 6445            2. This is a numbered list item that is
 6446            very long and needs to be wrapped
 6447            properly.
 6448            - This is an unordered list item that is
 6449            also very long and should not merge with
 6450            the numbered item.ˇ»
 6451        "},
 6452        indoc! {"
 6453            «1. This is a numbered list item that is
 6454               very long and needs to be wrapped
 6455               properly.
 6456            2. This is a numbered list item that is
 6457               very long and needs to be wrapped
 6458               properly.
 6459            - This is an unordered list item that is
 6460              also very long and should not merge
 6461              with the numbered item.ˇ»
 6462        "},
 6463        markdown_language.clone(),
 6464        &mut cx,
 6465    );
 6466
 6467    // Test that rewrapping maintain indents even when they already exists.
 6468    assert_rewrap(
 6469        indoc! {"
 6470            «1. This is a numbered list
 6471               item that is very long and needs to be wrapped properly.
 6472            2. This is a numbered list
 6473               item that is very long and needs to be wrapped properly.
 6474            - This is an unordered list item that is also very long and
 6475              should not merge with the numbered item.ˇ»
 6476        "},
 6477        indoc! {"
 6478            «1. This is a numbered list item that is
 6479               very long and needs to be wrapped
 6480               properly.
 6481            2. This is a numbered list item that is
 6482               very long and needs to be wrapped
 6483               properly.
 6484            - This is an unordered list item that is
 6485              also very long and should not merge
 6486              with the numbered item.ˇ»
 6487        "},
 6488        markdown_language,
 6489        &mut cx,
 6490    );
 6491
 6492    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6493    assert_rewrap(
 6494        indoc! {"
 6495            ˇThis is a very long line of plain text that will be wrapped.
 6496        "},
 6497        indoc! {"
 6498            ˇThis is a very long line of plain text
 6499            that will be wrapped.
 6500        "},
 6501        plaintext_language.clone(),
 6502        &mut cx,
 6503    );
 6504
 6505    // Test that non-commented code acts as a paragraph boundary within a selection
 6506    assert_rewrap(
 6507        indoc! {"
 6508               «// This is the first long comment block to be wrapped.
 6509               fn my_func(a: u32);
 6510               // This is the second long comment block to be wrapped.ˇ»
 6511           "},
 6512        indoc! {"
 6513               «// This is the first long comment block
 6514               // to be wrapped.
 6515               fn my_func(a: u32);
 6516               // This is the second long comment block
 6517               // to be wrapped.ˇ»
 6518           "},
 6519        rust_language,
 6520        &mut cx,
 6521    );
 6522
 6523    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6524    assert_rewrap(
 6525        indoc! {"
 6526            «ˇThis is a very long line that will be wrapped.
 6527
 6528            This is another paragraph in the same selection.»
 6529
 6530            «\tThis is a very long indented line that will be wrapped.ˇ»
 6531         "},
 6532        indoc! {"
 6533            «ˇThis is a very long line that will be
 6534            wrapped.
 6535
 6536            This is another paragraph in the same
 6537            selection.»
 6538
 6539            «\tThis is a very long indented line
 6540            \tthat will be wrapped.ˇ»
 6541         "},
 6542        plaintext_language,
 6543        &mut cx,
 6544    );
 6545
 6546    // Test that an empty comment line acts as a paragraph boundary
 6547    assert_rewrap(
 6548        indoc! {"
 6549            // ˇThis is a long comment that will be wrapped.
 6550            //
 6551            // And this is another long comment that will also be wrapped.ˇ
 6552         "},
 6553        indoc! {"
 6554            // ˇThis is a long comment that will be
 6555            // wrapped.
 6556            //
 6557            // And this is another long comment that
 6558            // will also be wrapped.ˇ
 6559         "},
 6560        cpp_language,
 6561        &mut cx,
 6562    );
 6563
 6564    #[track_caller]
 6565    fn assert_rewrap(
 6566        unwrapped_text: &str,
 6567        wrapped_text: &str,
 6568        language: Arc<Language>,
 6569        cx: &mut EditorTestContext,
 6570    ) {
 6571        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6572        cx.set_state(unwrapped_text);
 6573        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6574        cx.assert_editor_state(wrapped_text);
 6575    }
 6576}
 6577
 6578#[gpui::test]
 6579async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6580    init_test(cx, |settings| {
 6581        settings.languages.0.extend([(
 6582            "Rust".into(),
 6583            LanguageSettingsContent {
 6584                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6585                preferred_line_length: Some(40),
 6586                ..Default::default()
 6587            },
 6588        )])
 6589    });
 6590
 6591    let mut cx = EditorTestContext::new(cx).await;
 6592
 6593    let rust_lang = Arc::new(
 6594        Language::new(
 6595            LanguageConfig {
 6596                name: "Rust".into(),
 6597                line_comments: vec!["// ".into()],
 6598                block_comment: Some(BlockCommentConfig {
 6599                    start: "/*".into(),
 6600                    end: "*/".into(),
 6601                    prefix: "* ".into(),
 6602                    tab_size: 1,
 6603                }),
 6604                documentation_comment: Some(BlockCommentConfig {
 6605                    start: "/**".into(),
 6606                    end: "*/".into(),
 6607                    prefix: "* ".into(),
 6608                    tab_size: 1,
 6609                }),
 6610
 6611                ..LanguageConfig::default()
 6612            },
 6613            Some(tree_sitter_rust::LANGUAGE.into()),
 6614        )
 6615        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6616        .unwrap(),
 6617    );
 6618
 6619    // regular block comment
 6620    assert_rewrap(
 6621        indoc! {"
 6622            /*
 6623             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6624             */
 6625            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6626        "},
 6627        indoc! {"
 6628            /*
 6629             *ˇ Lorem ipsum dolor sit amet,
 6630             * consectetur adipiscing elit.
 6631             */
 6632            /*
 6633             *ˇ Lorem ipsum dolor sit amet,
 6634             * consectetur adipiscing elit.
 6635             */
 6636        "},
 6637        rust_lang.clone(),
 6638        &mut cx,
 6639    );
 6640
 6641    // indent is respected
 6642    assert_rewrap(
 6643        indoc! {"
 6644            {}
 6645                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6646        "},
 6647        indoc! {"
 6648            {}
 6649                /*
 6650                 *ˇ Lorem ipsum dolor sit amet,
 6651                 * consectetur adipiscing elit.
 6652                 */
 6653        "},
 6654        rust_lang.clone(),
 6655        &mut cx,
 6656    );
 6657
 6658    // short block comments with inline delimiters
 6659    assert_rewrap(
 6660        indoc! {"
 6661            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6662            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6663             */
 6664            /*
 6665             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6666        "},
 6667        indoc! {"
 6668            /*
 6669             *ˇ Lorem ipsum dolor sit amet,
 6670             * consectetur adipiscing elit.
 6671             */
 6672            /*
 6673             *ˇ Lorem ipsum dolor sit amet,
 6674             * consectetur adipiscing elit.
 6675             */
 6676            /*
 6677             *ˇ Lorem ipsum dolor sit amet,
 6678             * consectetur adipiscing elit.
 6679             */
 6680        "},
 6681        rust_lang.clone(),
 6682        &mut cx,
 6683    );
 6684
 6685    // multiline block comment with inline start/end delimiters
 6686    assert_rewrap(
 6687        indoc! {"
 6688            /*ˇ Lorem ipsum dolor sit amet,
 6689             * consectetur adipiscing elit. */
 6690        "},
 6691        indoc! {"
 6692            /*
 6693             *ˇ Lorem ipsum dolor sit amet,
 6694             * consectetur adipiscing elit.
 6695             */
 6696        "},
 6697        rust_lang.clone(),
 6698        &mut cx,
 6699    );
 6700
 6701    // block comment rewrap still respects paragraph bounds
 6702    assert_rewrap(
 6703        indoc! {"
 6704            /*
 6705             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6706             *
 6707             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6708             */
 6709        "},
 6710        indoc! {"
 6711            /*
 6712             *ˇ Lorem ipsum dolor sit amet,
 6713             * consectetur adipiscing elit.
 6714             *
 6715             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6716             */
 6717        "},
 6718        rust_lang.clone(),
 6719        &mut cx,
 6720    );
 6721
 6722    // documentation comments
 6723    assert_rewrap(
 6724        indoc! {"
 6725            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6726            /**
 6727             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6728             */
 6729        "},
 6730        indoc! {"
 6731            /**
 6732             *ˇ Lorem ipsum dolor sit amet,
 6733             * consectetur adipiscing elit.
 6734             */
 6735            /**
 6736             *ˇ Lorem ipsum dolor sit amet,
 6737             * consectetur adipiscing elit.
 6738             */
 6739        "},
 6740        rust_lang.clone(),
 6741        &mut cx,
 6742    );
 6743
 6744    // different, adjacent comments
 6745    assert_rewrap(
 6746        indoc! {"
 6747            /**
 6748             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6749             */
 6750            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6751            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6752        "},
 6753        indoc! {"
 6754            /**
 6755             *ˇ Lorem ipsum dolor sit amet,
 6756             * consectetur adipiscing elit.
 6757             */
 6758            /*
 6759             *ˇ Lorem ipsum dolor sit amet,
 6760             * consectetur adipiscing elit.
 6761             */
 6762            //ˇ Lorem ipsum dolor sit amet,
 6763            // consectetur adipiscing elit.
 6764        "},
 6765        rust_lang.clone(),
 6766        &mut cx,
 6767    );
 6768
 6769    // selection w/ single short block comment
 6770    assert_rewrap(
 6771        indoc! {"
 6772            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6773        "},
 6774        indoc! {"
 6775            «/*
 6776             * Lorem ipsum dolor sit amet,
 6777             * consectetur adipiscing elit.
 6778             */ˇ»
 6779        "},
 6780        rust_lang.clone(),
 6781        &mut cx,
 6782    );
 6783
 6784    // rewrapping a single comment w/ abutting comments
 6785    assert_rewrap(
 6786        indoc! {"
 6787            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6788            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6789        "},
 6790        indoc! {"
 6791            /*
 6792             * ˇLorem ipsum dolor sit amet,
 6793             * consectetur adipiscing elit.
 6794             */
 6795            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6796        "},
 6797        rust_lang.clone(),
 6798        &mut cx,
 6799    );
 6800
 6801    // selection w/ non-abutting short block comments
 6802    assert_rewrap(
 6803        indoc! {"
 6804            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6805
 6806            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6807        "},
 6808        indoc! {"
 6809            «/*
 6810             * Lorem ipsum dolor sit amet,
 6811             * consectetur adipiscing elit.
 6812             */
 6813
 6814            /*
 6815             * Lorem ipsum dolor sit amet,
 6816             * consectetur adipiscing elit.
 6817             */ˇ»
 6818        "},
 6819        rust_lang.clone(),
 6820        &mut cx,
 6821    );
 6822
 6823    // selection of multiline block comments
 6824    assert_rewrap(
 6825        indoc! {"
 6826            «/* Lorem ipsum dolor sit amet,
 6827             * consectetur adipiscing elit. */ˇ»
 6828        "},
 6829        indoc! {"
 6830            «/*
 6831             * Lorem ipsum dolor sit amet,
 6832             * consectetur adipiscing elit.
 6833             */ˇ»
 6834        "},
 6835        rust_lang.clone(),
 6836        &mut cx,
 6837    );
 6838
 6839    // partial selection of multiline block comments
 6840    assert_rewrap(
 6841        indoc! {"
 6842            «/* Lorem ipsum dolor sit amet,ˇ»
 6843             * consectetur adipiscing elit. */
 6844            /* Lorem ipsum dolor sit amet,
 6845             «* consectetur adipiscing elit. */ˇ»
 6846        "},
 6847        indoc! {"
 6848            «/*
 6849             * Lorem ipsum dolor sit amet,ˇ»
 6850             * consectetur adipiscing elit. */
 6851            /* Lorem ipsum dolor sit amet,
 6852             «* consectetur adipiscing elit.
 6853             */ˇ»
 6854        "},
 6855        rust_lang.clone(),
 6856        &mut cx,
 6857    );
 6858
 6859    // selection w/ abutting short block comments
 6860    // TODO: should not be combined; should rewrap as 2 comments
 6861    assert_rewrap(
 6862        indoc! {"
 6863            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6864            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6865        "},
 6866        // desired behavior:
 6867        // indoc! {"
 6868        //     «/*
 6869        //      * Lorem ipsum dolor sit amet,
 6870        //      * consectetur adipiscing elit.
 6871        //      */
 6872        //     /*
 6873        //      * Lorem ipsum dolor sit amet,
 6874        //      * consectetur adipiscing elit.
 6875        //      */ˇ»
 6876        // "},
 6877        // actual behaviour:
 6878        indoc! {"
 6879            «/*
 6880             * Lorem ipsum dolor sit amet,
 6881             * consectetur adipiscing elit. Lorem
 6882             * ipsum dolor sit amet, consectetur
 6883             * adipiscing elit.
 6884             */ˇ»
 6885        "},
 6886        rust_lang.clone(),
 6887        &mut cx,
 6888    );
 6889
 6890    // TODO: same as above, but with delimiters on separate line
 6891    // assert_rewrap(
 6892    //     indoc! {"
 6893    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6894    //          */
 6895    //         /*
 6896    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6897    //     "},
 6898    //     // desired:
 6899    //     // indoc! {"
 6900    //     //     «/*
 6901    //     //      * Lorem ipsum dolor sit amet,
 6902    //     //      * consectetur adipiscing elit.
 6903    //     //      */
 6904    //     //     /*
 6905    //     //      * Lorem ipsum dolor sit amet,
 6906    //     //      * consectetur adipiscing elit.
 6907    //     //      */ˇ»
 6908    //     // "},
 6909    //     // actual: (but with trailing w/s on the empty lines)
 6910    //     indoc! {"
 6911    //         «/*
 6912    //          * Lorem ipsum dolor sit amet,
 6913    //          * consectetur adipiscing elit.
 6914    //          *
 6915    //          */
 6916    //         /*
 6917    //          *
 6918    //          * Lorem ipsum dolor sit amet,
 6919    //          * consectetur adipiscing elit.
 6920    //          */ˇ»
 6921    //     "},
 6922    //     rust_lang.clone(),
 6923    //     &mut cx,
 6924    // );
 6925
 6926    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6927    assert_rewrap(
 6928        indoc! {"
 6929            /*
 6930             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6931             */
 6932            /*
 6933             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6934            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6935        "},
 6936        // desired:
 6937        // indoc! {"
 6938        //     /*
 6939        //      *ˇ Lorem ipsum dolor sit amet,
 6940        //      * consectetur adipiscing elit.
 6941        //      */
 6942        //     /*
 6943        //      *ˇ Lorem ipsum dolor sit amet,
 6944        //      * consectetur adipiscing elit.
 6945        //      */
 6946        //     /*
 6947        //      *ˇ Lorem ipsum dolor sit amet
 6948        //      */ /* consectetur adipiscing elit. */
 6949        // "},
 6950        // actual:
 6951        indoc! {"
 6952            /*
 6953             //ˇ Lorem ipsum dolor sit amet,
 6954             // consectetur adipiscing elit.
 6955             */
 6956            /*
 6957             * //ˇ Lorem ipsum dolor sit amet,
 6958             * consectetur adipiscing elit.
 6959             */
 6960            /*
 6961             *ˇ Lorem ipsum dolor sit amet */ /*
 6962             * consectetur adipiscing elit.
 6963             */
 6964        "},
 6965        rust_lang,
 6966        &mut cx,
 6967    );
 6968
 6969    #[track_caller]
 6970    fn assert_rewrap(
 6971        unwrapped_text: &str,
 6972        wrapped_text: &str,
 6973        language: Arc<Language>,
 6974        cx: &mut EditorTestContext,
 6975    ) {
 6976        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6977        cx.set_state(unwrapped_text);
 6978        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6979        cx.assert_editor_state(wrapped_text);
 6980    }
 6981}
 6982
 6983#[gpui::test]
 6984async fn test_hard_wrap(cx: &mut TestAppContext) {
 6985    init_test(cx, |_| {});
 6986    let mut cx = EditorTestContext::new(cx).await;
 6987
 6988    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6989    cx.update_editor(|editor, _, cx| {
 6990        editor.set_hard_wrap(Some(14), cx);
 6991    });
 6992
 6993    cx.set_state(indoc!(
 6994        "
 6995        one two three ˇ
 6996        "
 6997    ));
 6998    cx.simulate_input("four");
 6999    cx.run_until_parked();
 7000
 7001    cx.assert_editor_state(indoc!(
 7002        "
 7003        one two three
 7004        fourˇ
 7005        "
 7006    ));
 7007
 7008    cx.update_editor(|editor, window, cx| {
 7009        editor.newline(&Default::default(), window, cx);
 7010    });
 7011    cx.run_until_parked();
 7012    cx.assert_editor_state(indoc!(
 7013        "
 7014        one two three
 7015        four
 7016        ˇ
 7017        "
 7018    ));
 7019
 7020    cx.simulate_input("five");
 7021    cx.run_until_parked();
 7022    cx.assert_editor_state(indoc!(
 7023        "
 7024        one two three
 7025        four
 7026        fiveˇ
 7027        "
 7028    ));
 7029
 7030    cx.update_editor(|editor, window, cx| {
 7031        editor.newline(&Default::default(), window, cx);
 7032    });
 7033    cx.run_until_parked();
 7034    cx.simulate_input("# ");
 7035    cx.run_until_parked();
 7036    cx.assert_editor_state(indoc!(
 7037        "
 7038        one two three
 7039        four
 7040        five
 7041        # ˇ
 7042        "
 7043    ));
 7044
 7045    cx.update_editor(|editor, window, cx| {
 7046        editor.newline(&Default::default(), window, cx);
 7047    });
 7048    cx.run_until_parked();
 7049    cx.assert_editor_state(indoc!(
 7050        "
 7051        one two three
 7052        four
 7053        five
 7054        #\x20
 7055 7056        "
 7057    ));
 7058
 7059    cx.simulate_input(" 6");
 7060    cx.run_until_parked();
 7061    cx.assert_editor_state(indoc!(
 7062        "
 7063        one two three
 7064        four
 7065        five
 7066        #
 7067        # 6ˇ
 7068        "
 7069    ));
 7070}
 7071
 7072#[gpui::test]
 7073async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7074    init_test(cx, |_| {});
 7075
 7076    let mut cx = EditorTestContext::new(cx).await;
 7077
 7078    cx.set_state(indoc! {"The quick brownˇ"});
 7079    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7080    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7081
 7082    cx.set_state(indoc! {"The emacs foxˇ"});
 7083    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7084    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7085
 7086    cx.set_state(indoc! {"
 7087        The quick« brownˇ»
 7088        fox jumps overˇ
 7089        the lazy dog"});
 7090    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7091    cx.assert_editor_state(indoc! {"
 7092        The quickˇ
 7093        ˇthe lazy dog"});
 7094
 7095    cx.set_state(indoc! {"
 7096        The quick« brownˇ»
 7097        fox jumps overˇ
 7098        the lazy dog"});
 7099    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7100    cx.assert_editor_state(indoc! {"
 7101        The quickˇ
 7102        fox jumps overˇthe lazy dog"});
 7103
 7104    cx.set_state(indoc! {"
 7105        The quick« brownˇ»
 7106        fox jumps overˇ
 7107        the lazy dog"});
 7108    cx.update_editor(|e, window, cx| {
 7109        e.cut_to_end_of_line(
 7110            &CutToEndOfLine {
 7111                stop_at_newlines: true,
 7112            },
 7113            window,
 7114            cx,
 7115        )
 7116    });
 7117    cx.assert_editor_state(indoc! {"
 7118        The quickˇ
 7119        fox jumps overˇ
 7120        the lazy dog"});
 7121
 7122    cx.set_state(indoc! {"
 7123        The quick« brownˇ»
 7124        fox jumps overˇ
 7125        the lazy dog"});
 7126    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7127    cx.assert_editor_state(indoc! {"
 7128        The quickˇ
 7129        fox jumps overˇthe lazy dog"});
 7130}
 7131
 7132#[gpui::test]
 7133async fn test_clipboard(cx: &mut TestAppContext) {
 7134    init_test(cx, |_| {});
 7135
 7136    let mut cx = EditorTestContext::new(cx).await;
 7137
 7138    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7139    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7140    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7141
 7142    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7143    cx.set_state("two ˇfour ˇsix ˇ");
 7144    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7145    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7146
 7147    // Paste again but with only two cursors. Since the number of cursors doesn't
 7148    // match the number of slices in the clipboard, the entire clipboard text
 7149    // is pasted at each cursor.
 7150    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7151    cx.update_editor(|e, window, cx| {
 7152        e.handle_input("( ", window, cx);
 7153        e.paste(&Paste, window, cx);
 7154        e.handle_input(") ", window, cx);
 7155    });
 7156    cx.assert_editor_state(
 7157        &([
 7158            "( one✅ ",
 7159            "three ",
 7160            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7161            "three ",
 7162            "five ) ˇ",
 7163        ]
 7164        .join("\n")),
 7165    );
 7166
 7167    // Cut with three selections, one of which is full-line.
 7168    cx.set_state(indoc! {"
 7169        1«2ˇ»3
 7170        4ˇ567
 7171        «8ˇ»9"});
 7172    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7173    cx.assert_editor_state(indoc! {"
 7174        1ˇ3
 7175        ˇ9"});
 7176
 7177    // Paste with three selections, noticing how the copied selection that was full-line
 7178    // gets inserted before the second cursor.
 7179    cx.set_state(indoc! {"
 7180        1ˇ3
 7181 7182        «oˇ»ne"});
 7183    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7184    cx.assert_editor_state(indoc! {"
 7185        12ˇ3
 7186        4567
 7187 7188        8ˇne"});
 7189
 7190    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7191    cx.set_state(indoc! {"
 7192        The quick brown
 7193        fox juˇmps over
 7194        the lazy dog"});
 7195    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7196    assert_eq!(
 7197        cx.read_from_clipboard()
 7198            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7199        Some("fox jumps over\n".to_string())
 7200    );
 7201
 7202    // Paste with three selections, noticing how the copied full-line selection is inserted
 7203    // before the empty selections but replaces the selection that is non-empty.
 7204    cx.set_state(indoc! {"
 7205        Tˇhe quick brown
 7206        «foˇ»x jumps over
 7207        tˇhe lazy dog"});
 7208    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7209    cx.assert_editor_state(indoc! {"
 7210        fox jumps over
 7211        Tˇhe quick brown
 7212        fox jumps over
 7213        ˇx jumps over
 7214        fox jumps over
 7215        tˇhe lazy dog"});
 7216}
 7217
 7218#[gpui::test]
 7219async fn test_copy_trim(cx: &mut TestAppContext) {
 7220    init_test(cx, |_| {});
 7221
 7222    let mut cx = EditorTestContext::new(cx).await;
 7223    cx.set_state(
 7224        r#"            «for selection in selections.iter() {
 7225            let mut start = selection.start;
 7226            let mut end = selection.end;
 7227            let is_entire_line = selection.is_empty();
 7228            if is_entire_line {
 7229                start = Point::new(start.row, 0);ˇ»
 7230                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7231            }
 7232        "#,
 7233    );
 7234    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7235    assert_eq!(
 7236        cx.read_from_clipboard()
 7237            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7238        Some(
 7239            "for selection in selections.iter() {
 7240            let mut start = selection.start;
 7241            let mut end = selection.end;
 7242            let is_entire_line = selection.is_empty();
 7243            if is_entire_line {
 7244                start = Point::new(start.row, 0);"
 7245                .to_string()
 7246        ),
 7247        "Regular copying preserves all indentation selected",
 7248    );
 7249    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7250    assert_eq!(
 7251        cx.read_from_clipboard()
 7252            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7253        Some(
 7254            "for selection in selections.iter() {
 7255let mut start = selection.start;
 7256let mut end = selection.end;
 7257let is_entire_line = selection.is_empty();
 7258if is_entire_line {
 7259    start = Point::new(start.row, 0);"
 7260                .to_string()
 7261        ),
 7262        "Copying with stripping should strip all leading whitespaces"
 7263    );
 7264
 7265    cx.set_state(
 7266        r#"       «     for selection in selections.iter() {
 7267            let mut start = selection.start;
 7268            let mut end = selection.end;
 7269            let is_entire_line = selection.is_empty();
 7270            if is_entire_line {
 7271                start = Point::new(start.row, 0);ˇ»
 7272                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7273            }
 7274        "#,
 7275    );
 7276    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7277    assert_eq!(
 7278        cx.read_from_clipboard()
 7279            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7280        Some(
 7281            "     for selection in selections.iter() {
 7282            let mut start = selection.start;
 7283            let mut end = selection.end;
 7284            let is_entire_line = selection.is_empty();
 7285            if is_entire_line {
 7286                start = Point::new(start.row, 0);"
 7287                .to_string()
 7288        ),
 7289        "Regular copying preserves all indentation selected",
 7290    );
 7291    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7292    assert_eq!(
 7293        cx.read_from_clipboard()
 7294            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7295        Some(
 7296            "for selection in selections.iter() {
 7297let mut start = selection.start;
 7298let mut end = selection.end;
 7299let is_entire_line = selection.is_empty();
 7300if is_entire_line {
 7301    start = Point::new(start.row, 0);"
 7302                .to_string()
 7303        ),
 7304        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7305    );
 7306
 7307    cx.set_state(
 7308        r#"       «ˇ     for selection in selections.iter() {
 7309            let mut start = selection.start;
 7310            let mut end = selection.end;
 7311            let is_entire_line = selection.is_empty();
 7312            if is_entire_line {
 7313                start = Point::new(start.row, 0);»
 7314                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7315            }
 7316        "#,
 7317    );
 7318    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7319    assert_eq!(
 7320        cx.read_from_clipboard()
 7321            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7322        Some(
 7323            "     for selection in selections.iter() {
 7324            let mut start = selection.start;
 7325            let mut end = selection.end;
 7326            let is_entire_line = selection.is_empty();
 7327            if is_entire_line {
 7328                start = Point::new(start.row, 0);"
 7329                .to_string()
 7330        ),
 7331        "Regular copying for reverse selection works the same",
 7332    );
 7333    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7334    assert_eq!(
 7335        cx.read_from_clipboard()
 7336            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7337        Some(
 7338            "for selection in selections.iter() {
 7339let mut start = selection.start;
 7340let mut end = selection.end;
 7341let is_entire_line = selection.is_empty();
 7342if is_entire_line {
 7343    start = Point::new(start.row, 0);"
 7344                .to_string()
 7345        ),
 7346        "Copying with stripping for reverse selection works the same"
 7347    );
 7348
 7349    cx.set_state(
 7350        r#"            for selection «in selections.iter() {
 7351            let mut start = selection.start;
 7352            let mut end = selection.end;
 7353            let is_entire_line = selection.is_empty();
 7354            if is_entire_line {
 7355                start = Point::new(start.row, 0);ˇ»
 7356                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7357            }
 7358        "#,
 7359    );
 7360    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7361    assert_eq!(
 7362        cx.read_from_clipboard()
 7363            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7364        Some(
 7365            "in selections.iter() {
 7366            let mut start = selection.start;
 7367            let mut end = selection.end;
 7368            let is_entire_line = selection.is_empty();
 7369            if is_entire_line {
 7370                start = Point::new(start.row, 0);"
 7371                .to_string()
 7372        ),
 7373        "When selecting past the indent, the copying works as usual",
 7374    );
 7375    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7376    assert_eq!(
 7377        cx.read_from_clipboard()
 7378            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7379        Some(
 7380            "in selections.iter() {
 7381            let mut start = selection.start;
 7382            let mut end = selection.end;
 7383            let is_entire_line = selection.is_empty();
 7384            if is_entire_line {
 7385                start = Point::new(start.row, 0);"
 7386                .to_string()
 7387        ),
 7388        "When selecting past the indent, nothing is trimmed"
 7389    );
 7390
 7391    cx.set_state(
 7392        r#"            «for selection in selections.iter() {
 7393            let mut start = selection.start;
 7394
 7395            let mut end = selection.end;
 7396            let is_entire_line = selection.is_empty();
 7397            if is_entire_line {
 7398                start = Point::new(start.row, 0);
 7399ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7400            }
 7401        "#,
 7402    );
 7403    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7404    assert_eq!(
 7405        cx.read_from_clipboard()
 7406            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7407        Some(
 7408            "for selection in selections.iter() {
 7409let mut start = selection.start;
 7410
 7411let mut end = selection.end;
 7412let is_entire_line = selection.is_empty();
 7413if is_entire_line {
 7414    start = Point::new(start.row, 0);
 7415"
 7416            .to_string()
 7417        ),
 7418        "Copying with stripping should ignore empty lines"
 7419    );
 7420}
 7421
 7422#[gpui::test]
 7423async fn test_paste_multiline(cx: &mut TestAppContext) {
 7424    init_test(cx, |_| {});
 7425
 7426    let mut cx = EditorTestContext::new(cx).await;
 7427    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7428
 7429    // Cut an indented block, without the leading whitespace.
 7430    cx.set_state(indoc! {"
 7431        const a: B = (
 7432            c(),
 7433            «d(
 7434                e,
 7435                f
 7436            )ˇ»
 7437        );
 7438    "});
 7439    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7440    cx.assert_editor_state(indoc! {"
 7441        const a: B = (
 7442            c(),
 7443            ˇ
 7444        );
 7445    "});
 7446
 7447    // Paste it at the same position.
 7448    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7449    cx.assert_editor_state(indoc! {"
 7450        const a: B = (
 7451            c(),
 7452            d(
 7453                e,
 7454                f
 7455 7456        );
 7457    "});
 7458
 7459    // Paste it at a line with a lower indent level.
 7460    cx.set_state(indoc! {"
 7461        ˇ
 7462        const a: B = (
 7463            c(),
 7464        );
 7465    "});
 7466    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7467    cx.assert_editor_state(indoc! {"
 7468        d(
 7469            e,
 7470            f
 7471 7472        const a: B = (
 7473            c(),
 7474        );
 7475    "});
 7476
 7477    // Cut an indented block, with the leading whitespace.
 7478    cx.set_state(indoc! {"
 7479        const a: B = (
 7480            c(),
 7481        «    d(
 7482                e,
 7483                f
 7484            )
 7485        ˇ»);
 7486    "});
 7487    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7488    cx.assert_editor_state(indoc! {"
 7489        const a: B = (
 7490            c(),
 7491        ˇ);
 7492    "});
 7493
 7494    // Paste it at the same position.
 7495    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7496    cx.assert_editor_state(indoc! {"
 7497        const a: B = (
 7498            c(),
 7499            d(
 7500                e,
 7501                f
 7502            )
 7503        ˇ);
 7504    "});
 7505
 7506    // Paste it at a line with a higher indent level.
 7507    cx.set_state(indoc! {"
 7508        const a: B = (
 7509            c(),
 7510            d(
 7511                e,
 7512 7513            )
 7514        );
 7515    "});
 7516    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7517    cx.assert_editor_state(indoc! {"
 7518        const a: B = (
 7519            c(),
 7520            d(
 7521                e,
 7522                f    d(
 7523                    e,
 7524                    f
 7525                )
 7526        ˇ
 7527            )
 7528        );
 7529    "});
 7530
 7531    // Copy an indented block, starting mid-line
 7532    cx.set_state(indoc! {"
 7533        const a: B = (
 7534            c(),
 7535            somethin«g(
 7536                e,
 7537                f
 7538            )ˇ»
 7539        );
 7540    "});
 7541    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7542
 7543    // Paste it on a line with a lower indent level
 7544    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7545    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7546    cx.assert_editor_state(indoc! {"
 7547        const a: B = (
 7548            c(),
 7549            something(
 7550                e,
 7551                f
 7552            )
 7553        );
 7554        g(
 7555            e,
 7556            f
 7557"});
 7558}
 7559
 7560#[gpui::test]
 7561async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7562    init_test(cx, |_| {});
 7563
 7564    cx.write_to_clipboard(ClipboardItem::new_string(
 7565        "    d(\n        e\n    );\n".into(),
 7566    ));
 7567
 7568    let mut cx = EditorTestContext::new(cx).await;
 7569    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7570
 7571    cx.set_state(indoc! {"
 7572        fn a() {
 7573            b();
 7574            if c() {
 7575                ˇ
 7576            }
 7577        }
 7578    "});
 7579
 7580    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7581    cx.assert_editor_state(indoc! {"
 7582        fn a() {
 7583            b();
 7584            if c() {
 7585                d(
 7586                    e
 7587                );
 7588        ˇ
 7589            }
 7590        }
 7591    "});
 7592
 7593    cx.set_state(indoc! {"
 7594        fn a() {
 7595            b();
 7596            ˇ
 7597        }
 7598    "});
 7599
 7600    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7601    cx.assert_editor_state(indoc! {"
 7602        fn a() {
 7603            b();
 7604            d(
 7605                e
 7606            );
 7607        ˇ
 7608        }
 7609    "});
 7610}
 7611
 7612#[gpui::test]
 7613fn test_select_all(cx: &mut TestAppContext) {
 7614    init_test(cx, |_| {});
 7615
 7616    let editor = cx.add_window(|window, cx| {
 7617        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7618        build_editor(buffer, window, cx)
 7619    });
 7620    _ = editor.update(cx, |editor, window, cx| {
 7621        editor.select_all(&SelectAll, window, cx);
 7622        assert_eq!(
 7623            display_ranges(editor, cx),
 7624            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7625        );
 7626    });
 7627}
 7628
 7629#[gpui::test]
 7630fn test_select_line(cx: &mut TestAppContext) {
 7631    init_test(cx, |_| {});
 7632
 7633    let editor = cx.add_window(|window, cx| {
 7634        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7635        build_editor(buffer, window, cx)
 7636    });
 7637    _ = editor.update(cx, |editor, window, cx| {
 7638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7639            s.select_display_ranges([
 7640                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7641                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7642                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7643                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7644            ])
 7645        });
 7646        editor.select_line(&SelectLine, window, cx);
 7647        assert_eq!(
 7648            display_ranges(editor, cx),
 7649            vec![
 7650                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7651                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7652            ]
 7653        );
 7654    });
 7655
 7656    _ = editor.update(cx, |editor, window, cx| {
 7657        editor.select_line(&SelectLine, window, cx);
 7658        assert_eq!(
 7659            display_ranges(editor, cx),
 7660            vec![
 7661                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7662                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7663            ]
 7664        );
 7665    });
 7666
 7667    _ = editor.update(cx, |editor, window, cx| {
 7668        editor.select_line(&SelectLine, window, cx);
 7669        assert_eq!(
 7670            display_ranges(editor, cx),
 7671            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7672        );
 7673    });
 7674}
 7675
 7676#[gpui::test]
 7677async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7678    init_test(cx, |_| {});
 7679    let mut cx = EditorTestContext::new(cx).await;
 7680
 7681    #[track_caller]
 7682    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7683        cx.set_state(initial_state);
 7684        cx.update_editor(|e, window, cx| {
 7685            e.split_selection_into_lines(&Default::default(), window, cx)
 7686        });
 7687        cx.assert_editor_state(expected_state);
 7688    }
 7689
 7690    // Selection starts and ends at the middle of lines, left-to-right
 7691    test(
 7692        &mut cx,
 7693        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7694        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7695    );
 7696    // Same thing, right-to-left
 7697    test(
 7698        &mut cx,
 7699        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7700        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7701    );
 7702
 7703    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7704    test(
 7705        &mut cx,
 7706        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7707        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7708    );
 7709    // Same thing, right-to-left
 7710    test(
 7711        &mut cx,
 7712        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7713        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7714    );
 7715
 7716    // Whole buffer, left-to-right, last line ends with newline
 7717    test(
 7718        &mut cx,
 7719        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7720        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7721    );
 7722    // Same thing, right-to-left
 7723    test(
 7724        &mut cx,
 7725        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7726        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7727    );
 7728
 7729    // Starts at the end of a line, ends at the start of another
 7730    test(
 7731        &mut cx,
 7732        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7733        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7734    );
 7735}
 7736
 7737#[gpui::test]
 7738async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7739    init_test(cx, |_| {});
 7740
 7741    let editor = cx.add_window(|window, cx| {
 7742        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7743        build_editor(buffer, window, cx)
 7744    });
 7745
 7746    // setup
 7747    _ = editor.update(cx, |editor, window, cx| {
 7748        editor.fold_creases(
 7749            vec![
 7750                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7751                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7752                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7753            ],
 7754            true,
 7755            window,
 7756            cx,
 7757        );
 7758        assert_eq!(
 7759            editor.display_text(cx),
 7760            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7761        );
 7762    });
 7763
 7764    _ = editor.update(cx, |editor, window, cx| {
 7765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7766            s.select_display_ranges([
 7767                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7768                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7769                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7770                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7771            ])
 7772        });
 7773        editor.split_selection_into_lines(&Default::default(), window, cx);
 7774        assert_eq!(
 7775            editor.display_text(cx),
 7776            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7777        );
 7778    });
 7779    EditorTestContext::for_editor(editor, cx)
 7780        .await
 7781        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7782
 7783    _ = editor.update(cx, |editor, window, cx| {
 7784        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7785            s.select_display_ranges([
 7786                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7787            ])
 7788        });
 7789        editor.split_selection_into_lines(&Default::default(), window, cx);
 7790        assert_eq!(
 7791            editor.display_text(cx),
 7792            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7793        );
 7794        assert_eq!(
 7795            display_ranges(editor, cx),
 7796            [
 7797                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7798                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7799                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7800                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7801                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7802                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7803                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7804            ]
 7805        );
 7806    });
 7807    EditorTestContext::for_editor(editor, cx)
 7808        .await
 7809        .assert_editor_state(
 7810            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7811        );
 7812}
 7813
 7814#[gpui::test]
 7815async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7816    init_test(cx, |_| {});
 7817
 7818    let mut cx = EditorTestContext::new(cx).await;
 7819
 7820    cx.set_state(indoc!(
 7821        r#"abc
 7822           defˇghi
 7823
 7824           jk
 7825           nlmo
 7826           "#
 7827    ));
 7828
 7829    cx.update_editor(|editor, window, cx| {
 7830        editor.add_selection_above(&Default::default(), window, cx);
 7831    });
 7832
 7833    cx.assert_editor_state(indoc!(
 7834        r#"abcˇ
 7835           defˇghi
 7836
 7837           jk
 7838           nlmo
 7839           "#
 7840    ));
 7841
 7842    cx.update_editor(|editor, window, cx| {
 7843        editor.add_selection_above(&Default::default(), window, cx);
 7844    });
 7845
 7846    cx.assert_editor_state(indoc!(
 7847        r#"abcˇ
 7848            defˇghi
 7849
 7850            jk
 7851            nlmo
 7852            "#
 7853    ));
 7854
 7855    cx.update_editor(|editor, window, cx| {
 7856        editor.add_selection_below(&Default::default(), window, cx);
 7857    });
 7858
 7859    cx.assert_editor_state(indoc!(
 7860        r#"abc
 7861           defˇghi
 7862
 7863           jk
 7864           nlmo
 7865           "#
 7866    ));
 7867
 7868    cx.update_editor(|editor, window, cx| {
 7869        editor.undo_selection(&Default::default(), window, cx);
 7870    });
 7871
 7872    cx.assert_editor_state(indoc!(
 7873        r#"abcˇ
 7874           defˇghi
 7875
 7876           jk
 7877           nlmo
 7878           "#
 7879    ));
 7880
 7881    cx.update_editor(|editor, window, cx| {
 7882        editor.redo_selection(&Default::default(), window, cx);
 7883    });
 7884
 7885    cx.assert_editor_state(indoc!(
 7886        r#"abc
 7887           defˇghi
 7888
 7889           jk
 7890           nlmo
 7891           "#
 7892    ));
 7893
 7894    cx.update_editor(|editor, window, cx| {
 7895        editor.add_selection_below(&Default::default(), window, cx);
 7896    });
 7897
 7898    cx.assert_editor_state(indoc!(
 7899        r#"abc
 7900           defˇghi
 7901           ˇ
 7902           jk
 7903           nlmo
 7904           "#
 7905    ));
 7906
 7907    cx.update_editor(|editor, window, cx| {
 7908        editor.add_selection_below(&Default::default(), window, cx);
 7909    });
 7910
 7911    cx.assert_editor_state(indoc!(
 7912        r#"abc
 7913           defˇghi
 7914           ˇ
 7915           jkˇ
 7916           nlmo
 7917           "#
 7918    ));
 7919
 7920    cx.update_editor(|editor, window, cx| {
 7921        editor.add_selection_below(&Default::default(), window, cx);
 7922    });
 7923
 7924    cx.assert_editor_state(indoc!(
 7925        r#"abc
 7926           defˇghi
 7927           ˇ
 7928           jkˇ
 7929           nlmˇo
 7930           "#
 7931    ));
 7932
 7933    cx.update_editor(|editor, window, cx| {
 7934        editor.add_selection_below(&Default::default(), window, cx);
 7935    });
 7936
 7937    cx.assert_editor_state(indoc!(
 7938        r#"abc
 7939           defˇghi
 7940           ˇ
 7941           jkˇ
 7942           nlmˇo
 7943           ˇ"#
 7944    ));
 7945
 7946    // change selections
 7947    cx.set_state(indoc!(
 7948        r#"abc
 7949           def«ˇg»hi
 7950
 7951           jk
 7952           nlmo
 7953           "#
 7954    ));
 7955
 7956    cx.update_editor(|editor, window, cx| {
 7957        editor.add_selection_below(&Default::default(), window, cx);
 7958    });
 7959
 7960    cx.assert_editor_state(indoc!(
 7961        r#"abc
 7962           def«ˇg»hi
 7963
 7964           jk
 7965           nlm«ˇo»
 7966           "#
 7967    ));
 7968
 7969    cx.update_editor(|editor, window, cx| {
 7970        editor.add_selection_below(&Default::default(), window, cx);
 7971    });
 7972
 7973    cx.assert_editor_state(indoc!(
 7974        r#"abc
 7975           def«ˇg»hi
 7976
 7977           jk
 7978           nlm«ˇo»
 7979           "#
 7980    ));
 7981
 7982    cx.update_editor(|editor, window, cx| {
 7983        editor.add_selection_above(&Default::default(), window, cx);
 7984    });
 7985
 7986    cx.assert_editor_state(indoc!(
 7987        r#"abc
 7988           def«ˇg»hi
 7989
 7990           jk
 7991           nlmo
 7992           "#
 7993    ));
 7994
 7995    cx.update_editor(|editor, window, cx| {
 7996        editor.add_selection_above(&Default::default(), window, cx);
 7997    });
 7998
 7999    cx.assert_editor_state(indoc!(
 8000        r#"abc
 8001           def«ˇg»hi
 8002
 8003           jk
 8004           nlmo
 8005           "#
 8006    ));
 8007
 8008    // Change selections again
 8009    cx.set_state(indoc!(
 8010        r#"a«bc
 8011           defgˇ»hi
 8012
 8013           jk
 8014           nlmo
 8015           "#
 8016    ));
 8017
 8018    cx.update_editor(|editor, window, cx| {
 8019        editor.add_selection_below(&Default::default(), window, cx);
 8020    });
 8021
 8022    cx.assert_editor_state(indoc!(
 8023        r#"a«bcˇ»
 8024           d«efgˇ»hi
 8025
 8026           j«kˇ»
 8027           nlmo
 8028           "#
 8029    ));
 8030
 8031    cx.update_editor(|editor, window, cx| {
 8032        editor.add_selection_below(&Default::default(), window, cx);
 8033    });
 8034    cx.assert_editor_state(indoc!(
 8035        r#"a«bcˇ»
 8036           d«efgˇ»hi
 8037
 8038           j«kˇ»
 8039           n«lmoˇ»
 8040           "#
 8041    ));
 8042    cx.update_editor(|editor, window, cx| {
 8043        editor.add_selection_above(&Default::default(), window, cx);
 8044    });
 8045
 8046    cx.assert_editor_state(indoc!(
 8047        r#"a«bcˇ»
 8048           d«efgˇ»hi
 8049
 8050           j«kˇ»
 8051           nlmo
 8052           "#
 8053    ));
 8054
 8055    // Change selections again
 8056    cx.set_state(indoc!(
 8057        r#"abc
 8058           d«ˇefghi
 8059
 8060           jk
 8061           nlm»o
 8062           "#
 8063    ));
 8064
 8065    cx.update_editor(|editor, window, cx| {
 8066        editor.add_selection_above(&Default::default(), window, cx);
 8067    });
 8068
 8069    cx.assert_editor_state(indoc!(
 8070        r#"a«ˇbc»
 8071           d«ˇef»ghi
 8072
 8073           j«ˇk»
 8074           n«ˇlm»o
 8075           "#
 8076    ));
 8077
 8078    cx.update_editor(|editor, window, cx| {
 8079        editor.add_selection_below(&Default::default(), window, cx);
 8080    });
 8081
 8082    cx.assert_editor_state(indoc!(
 8083        r#"abc
 8084           d«ˇef»ghi
 8085
 8086           j«ˇk»
 8087           n«ˇlm»o
 8088           "#
 8089    ));
 8090}
 8091
 8092#[gpui::test]
 8093async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8094    init_test(cx, |_| {});
 8095    let mut cx = EditorTestContext::new(cx).await;
 8096
 8097    cx.set_state(indoc!(
 8098        r#"line onˇe
 8099           liˇne two
 8100           line three
 8101           line four"#
 8102    ));
 8103
 8104    cx.update_editor(|editor, window, cx| {
 8105        editor.add_selection_below(&Default::default(), window, cx);
 8106    });
 8107
 8108    // test multiple cursors expand in the same direction
 8109    cx.assert_editor_state(indoc!(
 8110        r#"line onˇe
 8111           liˇne twˇo
 8112           liˇne three
 8113           line four"#
 8114    ));
 8115
 8116    cx.update_editor(|editor, window, cx| {
 8117        editor.add_selection_below(&Default::default(), window, cx);
 8118    });
 8119
 8120    cx.update_editor(|editor, window, cx| {
 8121        editor.add_selection_below(&Default::default(), window, cx);
 8122    });
 8123
 8124    // test multiple cursors expand below overflow
 8125    cx.assert_editor_state(indoc!(
 8126        r#"line onˇe
 8127           liˇne twˇo
 8128           liˇne thˇree
 8129           liˇne foˇur"#
 8130    ));
 8131
 8132    cx.update_editor(|editor, window, cx| {
 8133        editor.add_selection_above(&Default::default(), window, cx);
 8134    });
 8135
 8136    // test multiple cursors retrieves back correctly
 8137    cx.assert_editor_state(indoc!(
 8138        r#"line onˇe
 8139           liˇne twˇo
 8140           liˇne thˇree
 8141           line four"#
 8142    ));
 8143
 8144    cx.update_editor(|editor, window, cx| {
 8145        editor.add_selection_above(&Default::default(), window, cx);
 8146    });
 8147
 8148    cx.update_editor(|editor, window, cx| {
 8149        editor.add_selection_above(&Default::default(), window, cx);
 8150    });
 8151
 8152    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8153    cx.assert_editor_state(indoc!(
 8154        r#"liˇne onˇe
 8155           liˇne two
 8156           line three
 8157           line four"#
 8158    ));
 8159
 8160    cx.update_editor(|editor, window, cx| {
 8161        editor.undo_selection(&Default::default(), window, cx);
 8162    });
 8163
 8164    // test undo
 8165    cx.assert_editor_state(indoc!(
 8166        r#"line onˇe
 8167           liˇne twˇo
 8168           line three
 8169           line four"#
 8170    ));
 8171
 8172    cx.update_editor(|editor, window, cx| {
 8173        editor.redo_selection(&Default::default(), window, cx);
 8174    });
 8175
 8176    // test redo
 8177    cx.assert_editor_state(indoc!(
 8178        r#"liˇne onˇe
 8179           liˇne two
 8180           line three
 8181           line four"#
 8182    ));
 8183
 8184    cx.set_state(indoc!(
 8185        r#"abcd
 8186           ef«ghˇ»
 8187           ijkl
 8188           «mˇ»nop"#
 8189    ));
 8190
 8191    cx.update_editor(|editor, window, cx| {
 8192        editor.add_selection_above(&Default::default(), window, cx);
 8193    });
 8194
 8195    // test multiple selections expand in the same direction
 8196    cx.assert_editor_state(indoc!(
 8197        r#"ab«cdˇ»
 8198           ef«ghˇ»
 8199           «iˇ»jkl
 8200           «mˇ»nop"#
 8201    ));
 8202
 8203    cx.update_editor(|editor, window, cx| {
 8204        editor.add_selection_above(&Default::default(), window, cx);
 8205    });
 8206
 8207    // test multiple selection upward overflow
 8208    cx.assert_editor_state(indoc!(
 8209        r#"ab«cdˇ»
 8210           «eˇ»f«ghˇ»
 8211           «iˇ»jkl
 8212           «mˇ»nop"#
 8213    ));
 8214
 8215    cx.update_editor(|editor, window, cx| {
 8216        editor.add_selection_below(&Default::default(), window, cx);
 8217    });
 8218
 8219    // test multiple selection retrieves back correctly
 8220    cx.assert_editor_state(indoc!(
 8221        r#"abcd
 8222           ef«ghˇ»
 8223           «iˇ»jkl
 8224           «mˇ»nop"#
 8225    ));
 8226
 8227    cx.update_editor(|editor, window, cx| {
 8228        editor.add_selection_below(&Default::default(), window, cx);
 8229    });
 8230
 8231    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8232    cx.assert_editor_state(indoc!(
 8233        r#"abcd
 8234           ef«ghˇ»
 8235           ij«klˇ»
 8236           «mˇ»nop"#
 8237    ));
 8238
 8239    cx.update_editor(|editor, window, cx| {
 8240        editor.undo_selection(&Default::default(), window, cx);
 8241    });
 8242
 8243    // test undo
 8244    cx.assert_editor_state(indoc!(
 8245        r#"abcd
 8246           ef«ghˇ»
 8247           «iˇ»jkl
 8248           «mˇ»nop"#
 8249    ));
 8250
 8251    cx.update_editor(|editor, window, cx| {
 8252        editor.redo_selection(&Default::default(), window, cx);
 8253    });
 8254
 8255    // test redo
 8256    cx.assert_editor_state(indoc!(
 8257        r#"abcd
 8258           ef«ghˇ»
 8259           ij«klˇ»
 8260           «mˇ»nop"#
 8261    ));
 8262}
 8263
 8264#[gpui::test]
 8265async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8266    init_test(cx, |_| {});
 8267    let mut cx = EditorTestContext::new(cx).await;
 8268
 8269    cx.set_state(indoc!(
 8270        r#"line onˇe
 8271           liˇne two
 8272           line three
 8273           line four"#
 8274    ));
 8275
 8276    cx.update_editor(|editor, window, cx| {
 8277        editor.add_selection_below(&Default::default(), window, cx);
 8278        editor.add_selection_below(&Default::default(), window, cx);
 8279        editor.add_selection_below(&Default::default(), window, cx);
 8280    });
 8281
 8282    // initial state with two multi cursor groups
 8283    cx.assert_editor_state(indoc!(
 8284        r#"line onˇe
 8285           liˇne twˇo
 8286           liˇne thˇree
 8287           liˇne foˇur"#
 8288    ));
 8289
 8290    // add single cursor in middle - simulate opt click
 8291    cx.update_editor(|editor, window, cx| {
 8292        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8293        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8294        editor.end_selection(window, cx);
 8295    });
 8296
 8297    cx.assert_editor_state(indoc!(
 8298        r#"line onˇe
 8299           liˇne twˇo
 8300           liˇneˇ thˇree
 8301           liˇne foˇur"#
 8302    ));
 8303
 8304    cx.update_editor(|editor, window, cx| {
 8305        editor.add_selection_above(&Default::default(), window, cx);
 8306    });
 8307
 8308    // test new added selection expands above and existing selection shrinks
 8309    cx.assert_editor_state(indoc!(
 8310        r#"line onˇe
 8311           liˇneˇ twˇo
 8312           liˇneˇ thˇree
 8313           line four"#
 8314    ));
 8315
 8316    cx.update_editor(|editor, window, cx| {
 8317        editor.add_selection_above(&Default::default(), window, cx);
 8318    });
 8319
 8320    // test new added selection expands above and existing selection shrinks
 8321    cx.assert_editor_state(indoc!(
 8322        r#"lineˇ onˇe
 8323           liˇneˇ twˇo
 8324           lineˇ three
 8325           line four"#
 8326    ));
 8327
 8328    // intial state with two selection groups
 8329    cx.set_state(indoc!(
 8330        r#"abcd
 8331           ef«ghˇ»
 8332           ijkl
 8333           «mˇ»nop"#
 8334    ));
 8335
 8336    cx.update_editor(|editor, window, cx| {
 8337        editor.add_selection_above(&Default::default(), window, cx);
 8338        editor.add_selection_above(&Default::default(), window, cx);
 8339    });
 8340
 8341    cx.assert_editor_state(indoc!(
 8342        r#"ab«cdˇ»
 8343           «eˇ»f«ghˇ»
 8344           «iˇ»jkl
 8345           «mˇ»nop"#
 8346    ));
 8347
 8348    // add single selection in middle - simulate opt drag
 8349    cx.update_editor(|editor, window, cx| {
 8350        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8351        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8352        editor.update_selection(
 8353            DisplayPoint::new(DisplayRow(2), 4),
 8354            0,
 8355            gpui::Point::<f32>::default(),
 8356            window,
 8357            cx,
 8358        );
 8359        editor.end_selection(window, cx);
 8360    });
 8361
 8362    cx.assert_editor_state(indoc!(
 8363        r#"ab«cdˇ»
 8364           «eˇ»f«ghˇ»
 8365           «iˇ»jk«lˇ»
 8366           «mˇ»nop"#
 8367    ));
 8368
 8369    cx.update_editor(|editor, window, cx| {
 8370        editor.add_selection_below(&Default::default(), window, cx);
 8371    });
 8372
 8373    // test new added selection expands below, others shrinks from above
 8374    cx.assert_editor_state(indoc!(
 8375        r#"abcd
 8376           ef«ghˇ»
 8377           «iˇ»jk«lˇ»
 8378           «mˇ»no«pˇ»"#
 8379    ));
 8380}
 8381
 8382#[gpui::test]
 8383async fn test_select_next(cx: &mut TestAppContext) {
 8384    init_test(cx, |_| {});
 8385    let mut cx = EditorTestContext::new(cx).await;
 8386
 8387    // Enable case sensitive search.
 8388    update_test_editor_settings(&mut cx, |settings| {
 8389        let mut search_settings = SearchSettingsContent::default();
 8390        search_settings.case_sensitive = Some(true);
 8391        settings.search = Some(search_settings);
 8392    });
 8393
 8394    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8395
 8396    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8397        .unwrap();
 8398    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8399
 8400    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8401        .unwrap();
 8402    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8403
 8404    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8405    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8406
 8407    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8408    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8409
 8410    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8411        .unwrap();
 8412    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8413
 8414    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8415        .unwrap();
 8416    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8417
 8418    // Test selection direction should be preserved
 8419    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8420
 8421    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8422        .unwrap();
 8423    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8424
 8425    // Test case sensitivity
 8426    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8427    cx.update_editor(|e, window, cx| {
 8428        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8429    });
 8430    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8431
 8432    // Disable case sensitive search.
 8433    update_test_editor_settings(&mut cx, |settings| {
 8434        let mut search_settings = SearchSettingsContent::default();
 8435        search_settings.case_sensitive = Some(false);
 8436        settings.search = Some(search_settings);
 8437    });
 8438
 8439    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8440    cx.update_editor(|e, window, cx| {
 8441        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8442        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8443    });
 8444    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8445}
 8446
 8447#[gpui::test]
 8448async fn test_select_all_matches(cx: &mut TestAppContext) {
 8449    init_test(cx, |_| {});
 8450    let mut cx = EditorTestContext::new(cx).await;
 8451
 8452    // Enable case sensitive search.
 8453    update_test_editor_settings(&mut cx, |settings| {
 8454        let mut search_settings = SearchSettingsContent::default();
 8455        search_settings.case_sensitive = Some(true);
 8456        settings.search = Some(search_settings);
 8457    });
 8458
 8459    // Test caret-only selections
 8460    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8461    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8462        .unwrap();
 8463    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8464
 8465    // Test left-to-right selections
 8466    cx.set_state("abc\n«abcˇ»\nabc");
 8467    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8468        .unwrap();
 8469    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8470
 8471    // Test right-to-left selections
 8472    cx.set_state("abc\n«ˇabc»\nabc");
 8473    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8474        .unwrap();
 8475    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8476
 8477    // Test selecting whitespace with caret selection
 8478    cx.set_state("abc\nˇ   abc\nabc");
 8479    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8480        .unwrap();
 8481    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8482
 8483    // Test selecting whitespace with left-to-right selection
 8484    cx.set_state("abc\n«ˇ  »abc\nabc");
 8485    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8486        .unwrap();
 8487    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8488
 8489    // Test no matches with right-to-left selection
 8490    cx.set_state("abc\n«  ˇ»abc\nabc");
 8491    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8492        .unwrap();
 8493    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8494
 8495    // Test with a single word and clip_at_line_ends=true (#29823)
 8496    cx.set_state("aˇbc");
 8497    cx.update_editor(|e, window, cx| {
 8498        e.set_clip_at_line_ends(true, cx);
 8499        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8500        e.set_clip_at_line_ends(false, cx);
 8501    });
 8502    cx.assert_editor_state("«abcˇ»");
 8503
 8504    // Test case sensitivity
 8505    cx.set_state("fˇoo\nFOO\nFoo");
 8506    cx.update_editor(|e, window, cx| {
 8507        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8508    });
 8509    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8510
 8511    // Disable case sensitive search.
 8512    update_test_editor_settings(&mut cx, |settings| {
 8513        let mut search_settings = SearchSettingsContent::default();
 8514        search_settings.case_sensitive = Some(false);
 8515        settings.search = Some(search_settings);
 8516    });
 8517
 8518    cx.set_state("fˇoo\nFOO\nFoo");
 8519    cx.update_editor(|e, window, cx| {
 8520        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8521    });
 8522    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8523}
 8524
 8525#[gpui::test]
 8526async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8527    init_test(cx, |_| {});
 8528
 8529    let mut cx = EditorTestContext::new(cx).await;
 8530
 8531    let large_body_1 = "\nd".repeat(200);
 8532    let large_body_2 = "\ne".repeat(200);
 8533
 8534    cx.set_state(&format!(
 8535        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8536    ));
 8537    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8538        let scroll_position = editor.scroll_position(cx);
 8539        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8540        scroll_position
 8541    });
 8542
 8543    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8544        .unwrap();
 8545    cx.assert_editor_state(&format!(
 8546        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8547    ));
 8548    let scroll_position_after_selection =
 8549        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8550    assert_eq!(
 8551        initial_scroll_position, scroll_position_after_selection,
 8552        "Scroll position should not change after selecting all matches"
 8553    );
 8554}
 8555
 8556#[gpui::test]
 8557async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8558    init_test(cx, |_| {});
 8559
 8560    let mut cx = EditorLspTestContext::new_rust(
 8561        lsp::ServerCapabilities {
 8562            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8563            ..Default::default()
 8564        },
 8565        cx,
 8566    )
 8567    .await;
 8568
 8569    cx.set_state(indoc! {"
 8570        line 1
 8571        line 2
 8572        linˇe 3
 8573        line 4
 8574        line 5
 8575    "});
 8576
 8577    // Make an edit
 8578    cx.update_editor(|editor, window, cx| {
 8579        editor.handle_input("X", window, cx);
 8580    });
 8581
 8582    // Move cursor to a different position
 8583    cx.update_editor(|editor, window, cx| {
 8584        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8585            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8586        });
 8587    });
 8588
 8589    cx.assert_editor_state(indoc! {"
 8590        line 1
 8591        line 2
 8592        linXe 3
 8593        line 4
 8594        liˇne 5
 8595    "});
 8596
 8597    cx.lsp
 8598        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8599            Ok(Some(vec![lsp::TextEdit::new(
 8600                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8601                "PREFIX ".to_string(),
 8602            )]))
 8603        });
 8604
 8605    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8606        .unwrap()
 8607        .await
 8608        .unwrap();
 8609
 8610    cx.assert_editor_state(indoc! {"
 8611        PREFIX line 1
 8612        line 2
 8613        linXe 3
 8614        line 4
 8615        liˇne 5
 8616    "});
 8617
 8618    // Undo formatting
 8619    cx.update_editor(|editor, window, cx| {
 8620        editor.undo(&Default::default(), window, cx);
 8621    });
 8622
 8623    // Verify cursor moved back to position after edit
 8624    cx.assert_editor_state(indoc! {"
 8625        line 1
 8626        line 2
 8627        linXˇe 3
 8628        line 4
 8629        line 5
 8630    "});
 8631}
 8632
 8633#[gpui::test]
 8634async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8635    init_test(cx, |_| {});
 8636
 8637    let mut cx = EditorTestContext::new(cx).await;
 8638
 8639    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8640    cx.update_editor(|editor, window, cx| {
 8641        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8642    });
 8643
 8644    cx.set_state(indoc! {"
 8645        line 1
 8646        line 2
 8647        linˇe 3
 8648        line 4
 8649        line 5
 8650        line 6
 8651        line 7
 8652        line 8
 8653        line 9
 8654        line 10
 8655    "});
 8656
 8657    let snapshot = cx.buffer_snapshot();
 8658    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8659
 8660    cx.update(|_, cx| {
 8661        provider.update(cx, |provider, _| {
 8662            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8663                id: None,
 8664                edits: vec![(edit_position..edit_position, "X".into())],
 8665                edit_preview: None,
 8666            }))
 8667        })
 8668    });
 8669
 8670    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8671    cx.update_editor(|editor, window, cx| {
 8672        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8673    });
 8674
 8675    cx.assert_editor_state(indoc! {"
 8676        line 1
 8677        line 2
 8678        lineXˇ 3
 8679        line 4
 8680        line 5
 8681        line 6
 8682        line 7
 8683        line 8
 8684        line 9
 8685        line 10
 8686    "});
 8687
 8688    cx.update_editor(|editor, window, cx| {
 8689        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8690            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8691        });
 8692    });
 8693
 8694    cx.assert_editor_state(indoc! {"
 8695        line 1
 8696        line 2
 8697        lineX 3
 8698        line 4
 8699        line 5
 8700        line 6
 8701        line 7
 8702        line 8
 8703        line 9
 8704        liˇne 10
 8705    "});
 8706
 8707    cx.update_editor(|editor, window, cx| {
 8708        editor.undo(&Default::default(), window, cx);
 8709    });
 8710
 8711    cx.assert_editor_state(indoc! {"
 8712        line 1
 8713        line 2
 8714        lineˇ 3
 8715        line 4
 8716        line 5
 8717        line 6
 8718        line 7
 8719        line 8
 8720        line 9
 8721        line 10
 8722    "});
 8723}
 8724
 8725#[gpui::test]
 8726async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8727    init_test(cx, |_| {});
 8728
 8729    let mut cx = EditorTestContext::new(cx).await;
 8730    cx.set_state(
 8731        r#"let foo = 2;
 8732lˇet foo = 2;
 8733let fooˇ = 2;
 8734let foo = 2;
 8735let foo = ˇ2;"#,
 8736    );
 8737
 8738    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8739        .unwrap();
 8740    cx.assert_editor_state(
 8741        r#"let foo = 2;
 8742«letˇ» foo = 2;
 8743let «fooˇ» = 2;
 8744let foo = 2;
 8745let foo = «2ˇ»;"#,
 8746    );
 8747
 8748    // noop for multiple selections with different contents
 8749    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8750        .unwrap();
 8751    cx.assert_editor_state(
 8752        r#"let foo = 2;
 8753«letˇ» foo = 2;
 8754let «fooˇ» = 2;
 8755let foo = 2;
 8756let foo = «2ˇ»;"#,
 8757    );
 8758
 8759    // Test last selection direction should be preserved
 8760    cx.set_state(
 8761        r#"let foo = 2;
 8762let foo = 2;
 8763let «fooˇ» = 2;
 8764let «ˇfoo» = 2;
 8765let foo = 2;"#,
 8766    );
 8767
 8768    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8769        .unwrap();
 8770    cx.assert_editor_state(
 8771        r#"let foo = 2;
 8772let foo = 2;
 8773let «fooˇ» = 2;
 8774let «ˇfoo» = 2;
 8775let «ˇfoo» = 2;"#,
 8776    );
 8777}
 8778
 8779#[gpui::test]
 8780async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8781    init_test(cx, |_| {});
 8782
 8783    let mut cx =
 8784        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8785
 8786    cx.assert_editor_state(indoc! {"
 8787        ˇbbb
 8788        ccc
 8789
 8790        bbb
 8791        ccc
 8792        "});
 8793    cx.dispatch_action(SelectPrevious::default());
 8794    cx.assert_editor_state(indoc! {"
 8795                «bbbˇ»
 8796                ccc
 8797
 8798                bbb
 8799                ccc
 8800                "});
 8801    cx.dispatch_action(SelectPrevious::default());
 8802    cx.assert_editor_state(indoc! {"
 8803                «bbbˇ»
 8804                ccc
 8805
 8806                «bbbˇ»
 8807                ccc
 8808                "});
 8809}
 8810
 8811#[gpui::test]
 8812async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8813    init_test(cx, |_| {});
 8814
 8815    let mut cx = EditorTestContext::new(cx).await;
 8816    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8817
 8818    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8819        .unwrap();
 8820    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8821
 8822    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8823        .unwrap();
 8824    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8825
 8826    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8827    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8828
 8829    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8830    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8831
 8832    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8833        .unwrap();
 8834    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8835
 8836    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8837        .unwrap();
 8838    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8839}
 8840
 8841#[gpui::test]
 8842async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8843    init_test(cx, |_| {});
 8844
 8845    let mut cx = EditorTestContext::new(cx).await;
 8846    cx.set_state("");
 8847
 8848    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8849        .unwrap();
 8850    cx.assert_editor_state("«aˇ»");
 8851    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8852        .unwrap();
 8853    cx.assert_editor_state("«aˇ»");
 8854}
 8855
 8856#[gpui::test]
 8857async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8858    init_test(cx, |_| {});
 8859
 8860    let mut cx = EditorTestContext::new(cx).await;
 8861    cx.set_state(
 8862        r#"let foo = 2;
 8863lˇet foo = 2;
 8864let fooˇ = 2;
 8865let foo = 2;
 8866let foo = ˇ2;"#,
 8867    );
 8868
 8869    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8870        .unwrap();
 8871    cx.assert_editor_state(
 8872        r#"let foo = 2;
 8873«letˇ» foo = 2;
 8874let «fooˇ» = 2;
 8875let foo = 2;
 8876let foo = «2ˇ»;"#,
 8877    );
 8878
 8879    // noop for multiple selections with different contents
 8880    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8881        .unwrap();
 8882    cx.assert_editor_state(
 8883        r#"let foo = 2;
 8884«letˇ» foo = 2;
 8885let «fooˇ» = 2;
 8886let foo = 2;
 8887let foo = «2ˇ»;"#,
 8888    );
 8889}
 8890
 8891#[gpui::test]
 8892async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8893    init_test(cx, |_| {});
 8894    let mut cx = EditorTestContext::new(cx).await;
 8895
 8896    // Enable case sensitive search.
 8897    update_test_editor_settings(&mut cx, |settings| {
 8898        let mut search_settings = SearchSettingsContent::default();
 8899        search_settings.case_sensitive = Some(true);
 8900        settings.search = Some(search_settings);
 8901    });
 8902
 8903    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8904
 8905    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8906        .unwrap();
 8907    // selection direction is preserved
 8908    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8909
 8910    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8911        .unwrap();
 8912    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8913
 8914    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8915    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8916
 8917    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8918    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8919
 8920    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8921        .unwrap();
 8922    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8923
 8924    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8925        .unwrap();
 8926    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8927
 8928    // Test case sensitivity
 8929    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 8930    cx.update_editor(|e, window, cx| {
 8931        e.select_previous(&SelectPrevious::default(), window, cx)
 8932            .unwrap();
 8933        e.select_previous(&SelectPrevious::default(), window, cx)
 8934            .unwrap();
 8935    });
 8936    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8937
 8938    // Disable case sensitive search.
 8939    update_test_editor_settings(&mut cx, |settings| {
 8940        let mut search_settings = SearchSettingsContent::default();
 8941        search_settings.case_sensitive = Some(false);
 8942        settings.search = Some(search_settings);
 8943    });
 8944
 8945    cx.set_state("foo\nFOO\n«ˇFoo»");
 8946    cx.update_editor(|e, window, cx| {
 8947        e.select_previous(&SelectPrevious::default(), window, cx)
 8948            .unwrap();
 8949        e.select_previous(&SelectPrevious::default(), window, cx)
 8950            .unwrap();
 8951    });
 8952    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8953}
 8954
 8955#[gpui::test]
 8956async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8957    init_test(cx, |_| {});
 8958
 8959    let language = Arc::new(Language::new(
 8960        LanguageConfig::default(),
 8961        Some(tree_sitter_rust::LANGUAGE.into()),
 8962    ));
 8963
 8964    let text = r#"
 8965        use mod1::mod2::{mod3, mod4};
 8966
 8967        fn fn_1(param1: bool, param2: &str) {
 8968            let var1 = "text";
 8969        }
 8970    "#
 8971    .unindent();
 8972
 8973    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8974    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8975    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8976
 8977    editor
 8978        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8979        .await;
 8980
 8981    editor.update_in(cx, |editor, window, cx| {
 8982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8983            s.select_display_ranges([
 8984                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8985                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8986                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8987            ]);
 8988        });
 8989        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8990    });
 8991    editor.update(cx, |editor, cx| {
 8992        assert_text_with_selections(
 8993            editor,
 8994            indoc! {r#"
 8995                use mod1::mod2::{mod3, «mod4ˇ»};
 8996
 8997                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8998                    let var1 = "«ˇtext»";
 8999                }
 9000            "#},
 9001            cx,
 9002        );
 9003    });
 9004
 9005    editor.update_in(cx, |editor, window, cx| {
 9006        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9007    });
 9008    editor.update(cx, |editor, cx| {
 9009        assert_text_with_selections(
 9010            editor,
 9011            indoc! {r#"
 9012                use mod1::mod2::«{mod3, mod4}ˇ»;
 9013
 9014                «ˇfn fn_1(param1: bool, param2: &str) {
 9015                    let var1 = "text";
 9016 9017            "#},
 9018            cx,
 9019        );
 9020    });
 9021
 9022    editor.update_in(cx, |editor, window, cx| {
 9023        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9024    });
 9025    assert_eq!(
 9026        editor.update(cx, |editor, cx| editor
 9027            .selections
 9028            .display_ranges(&editor.display_snapshot(cx))),
 9029        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9030    );
 9031
 9032    // Trying to expand the selected syntax node one more time has no effect.
 9033    editor.update_in(cx, |editor, window, cx| {
 9034        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9035    });
 9036    assert_eq!(
 9037        editor.update(cx, |editor, cx| editor
 9038            .selections
 9039            .display_ranges(&editor.display_snapshot(cx))),
 9040        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9041    );
 9042
 9043    editor.update_in(cx, |editor, window, cx| {
 9044        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9045    });
 9046    editor.update(cx, |editor, cx| {
 9047        assert_text_with_selections(
 9048            editor,
 9049            indoc! {r#"
 9050                use mod1::mod2::«{mod3, mod4}ˇ»;
 9051
 9052                «ˇfn fn_1(param1: bool, param2: &str) {
 9053                    let var1 = "text";
 9054 9055            "#},
 9056            cx,
 9057        );
 9058    });
 9059
 9060    editor.update_in(cx, |editor, window, cx| {
 9061        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9062    });
 9063    editor.update(cx, |editor, cx| {
 9064        assert_text_with_selections(
 9065            editor,
 9066            indoc! {r#"
 9067                use mod1::mod2::{mod3, «mod4ˇ»};
 9068
 9069                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9070                    let var1 = "«ˇtext»";
 9071                }
 9072            "#},
 9073            cx,
 9074        );
 9075    });
 9076
 9077    editor.update_in(cx, |editor, window, cx| {
 9078        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9079    });
 9080    editor.update(cx, |editor, cx| {
 9081        assert_text_with_selections(
 9082            editor,
 9083            indoc! {r#"
 9084                use mod1::mod2::{mod3, moˇd4};
 9085
 9086                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9087                    let var1 = "teˇxt";
 9088                }
 9089            "#},
 9090            cx,
 9091        );
 9092    });
 9093
 9094    // Trying to shrink the selected syntax node one more time has no effect.
 9095    editor.update_in(cx, |editor, window, cx| {
 9096        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9097    });
 9098    editor.update_in(cx, |editor, _, cx| {
 9099        assert_text_with_selections(
 9100            editor,
 9101            indoc! {r#"
 9102                use mod1::mod2::{mod3, moˇd4};
 9103
 9104                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9105                    let var1 = "teˇxt";
 9106                }
 9107            "#},
 9108            cx,
 9109        );
 9110    });
 9111
 9112    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9113    // a fold.
 9114    editor.update_in(cx, |editor, window, cx| {
 9115        editor.fold_creases(
 9116            vec![
 9117                Crease::simple(
 9118                    Point::new(0, 21)..Point::new(0, 24),
 9119                    FoldPlaceholder::test(),
 9120                ),
 9121                Crease::simple(
 9122                    Point::new(3, 20)..Point::new(3, 22),
 9123                    FoldPlaceholder::test(),
 9124                ),
 9125            ],
 9126            true,
 9127            window,
 9128            cx,
 9129        );
 9130        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9131    });
 9132    editor.update(cx, |editor, cx| {
 9133        assert_text_with_selections(
 9134            editor,
 9135            indoc! {r#"
 9136                use mod1::mod2::«{mod3, mod4}ˇ»;
 9137
 9138                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9139                    let var1 = "«ˇtext»";
 9140                }
 9141            "#},
 9142            cx,
 9143        );
 9144    });
 9145}
 9146
 9147#[gpui::test]
 9148async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9149    init_test(cx, |_| {});
 9150
 9151    let language = Arc::new(Language::new(
 9152        LanguageConfig::default(),
 9153        Some(tree_sitter_rust::LANGUAGE.into()),
 9154    ));
 9155
 9156    let text = "let a = 2;";
 9157
 9158    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9159    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9160    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9161
 9162    editor
 9163        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9164        .await;
 9165
 9166    // Test case 1: Cursor at end of word
 9167    editor.update_in(cx, |editor, window, cx| {
 9168        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9169            s.select_display_ranges([
 9170                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9171            ]);
 9172        });
 9173    });
 9174    editor.update(cx, |editor, cx| {
 9175        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9176    });
 9177    editor.update_in(cx, |editor, window, cx| {
 9178        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9179    });
 9180    editor.update(cx, |editor, cx| {
 9181        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9182    });
 9183    editor.update_in(cx, |editor, window, cx| {
 9184        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9185    });
 9186    editor.update(cx, |editor, cx| {
 9187        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9188    });
 9189
 9190    // Test case 2: Cursor at end of statement
 9191    editor.update_in(cx, |editor, window, cx| {
 9192        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9193            s.select_display_ranges([
 9194                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9195            ]);
 9196        });
 9197    });
 9198    editor.update(cx, |editor, cx| {
 9199        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9200    });
 9201    editor.update_in(cx, |editor, window, cx| {
 9202        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9203    });
 9204    editor.update(cx, |editor, cx| {
 9205        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9206    });
 9207}
 9208
 9209#[gpui::test]
 9210async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9211    init_test(cx, |_| {});
 9212
 9213    let language = Arc::new(Language::new(
 9214        LanguageConfig {
 9215            name: "JavaScript".into(),
 9216            ..Default::default()
 9217        },
 9218        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9219    ));
 9220
 9221    let text = r#"
 9222        let a = {
 9223            key: "value",
 9224        };
 9225    "#
 9226    .unindent();
 9227
 9228    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9229    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9230    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9231
 9232    editor
 9233        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9234        .await;
 9235
 9236    // Test case 1: Cursor after '{'
 9237    editor.update_in(cx, |editor, window, cx| {
 9238        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9239            s.select_display_ranges([
 9240                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9241            ]);
 9242        });
 9243    });
 9244    editor.update(cx, |editor, cx| {
 9245        assert_text_with_selections(
 9246            editor,
 9247            indoc! {r#"
 9248                let a = {ˇ
 9249                    key: "value",
 9250                };
 9251            "#},
 9252            cx,
 9253        );
 9254    });
 9255    editor.update_in(cx, |editor, window, cx| {
 9256        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9257    });
 9258    editor.update(cx, |editor, cx| {
 9259        assert_text_with_selections(
 9260            editor,
 9261            indoc! {r#"
 9262                let a = «ˇ{
 9263                    key: "value",
 9264                }»;
 9265            "#},
 9266            cx,
 9267        );
 9268    });
 9269
 9270    // Test case 2: Cursor after ':'
 9271    editor.update_in(cx, |editor, window, cx| {
 9272        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9273            s.select_display_ranges([
 9274                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9275            ]);
 9276        });
 9277    });
 9278    editor.update(cx, |editor, cx| {
 9279        assert_text_with_selections(
 9280            editor,
 9281            indoc! {r#"
 9282                let a = {
 9283                    key:ˇ "value",
 9284                };
 9285            "#},
 9286            cx,
 9287        );
 9288    });
 9289    editor.update_in(cx, |editor, window, cx| {
 9290        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9291    });
 9292    editor.update(cx, |editor, cx| {
 9293        assert_text_with_selections(
 9294            editor,
 9295            indoc! {r#"
 9296                let a = {
 9297                    «ˇkey: "value"»,
 9298                };
 9299            "#},
 9300            cx,
 9301        );
 9302    });
 9303    editor.update_in(cx, |editor, window, cx| {
 9304        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9305    });
 9306    editor.update(cx, |editor, cx| {
 9307        assert_text_with_selections(
 9308            editor,
 9309            indoc! {r#"
 9310                let a = «ˇ{
 9311                    key: "value",
 9312                }»;
 9313            "#},
 9314            cx,
 9315        );
 9316    });
 9317
 9318    // Test case 3: Cursor after ','
 9319    editor.update_in(cx, |editor, window, cx| {
 9320        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9321            s.select_display_ranges([
 9322                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9323            ]);
 9324        });
 9325    });
 9326    editor.update(cx, |editor, cx| {
 9327        assert_text_with_selections(
 9328            editor,
 9329            indoc! {r#"
 9330                let a = {
 9331                    key: "value",ˇ
 9332                };
 9333            "#},
 9334            cx,
 9335        );
 9336    });
 9337    editor.update_in(cx, |editor, window, cx| {
 9338        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9339    });
 9340    editor.update(cx, |editor, cx| {
 9341        assert_text_with_selections(
 9342            editor,
 9343            indoc! {r#"
 9344                let a = «ˇ{
 9345                    key: "value",
 9346                }»;
 9347            "#},
 9348            cx,
 9349        );
 9350    });
 9351
 9352    // Test case 4: Cursor after ';'
 9353    editor.update_in(cx, |editor, window, cx| {
 9354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9355            s.select_display_ranges([
 9356                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9357            ]);
 9358        });
 9359    });
 9360    editor.update(cx, |editor, cx| {
 9361        assert_text_with_selections(
 9362            editor,
 9363            indoc! {r#"
 9364                let a = {
 9365                    key: "value",
 9366                };ˇ
 9367            "#},
 9368            cx,
 9369        );
 9370    });
 9371    editor.update_in(cx, |editor, window, cx| {
 9372        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9373    });
 9374    editor.update(cx, |editor, cx| {
 9375        assert_text_with_selections(
 9376            editor,
 9377            indoc! {r#"
 9378                «ˇlet a = {
 9379                    key: "value",
 9380                };
 9381                »"#},
 9382            cx,
 9383        );
 9384    });
 9385}
 9386
 9387#[gpui::test]
 9388async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9389    init_test(cx, |_| {});
 9390
 9391    let language = Arc::new(Language::new(
 9392        LanguageConfig::default(),
 9393        Some(tree_sitter_rust::LANGUAGE.into()),
 9394    ));
 9395
 9396    let text = r#"
 9397        use mod1::mod2::{mod3, mod4};
 9398
 9399        fn fn_1(param1: bool, param2: &str) {
 9400            let var1 = "hello world";
 9401        }
 9402    "#
 9403    .unindent();
 9404
 9405    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9406    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9407    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9408
 9409    editor
 9410        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9411        .await;
 9412
 9413    // Test 1: Cursor on a letter of a string word
 9414    editor.update_in(cx, |editor, window, cx| {
 9415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9416            s.select_display_ranges([
 9417                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9418            ]);
 9419        });
 9420    });
 9421    editor.update_in(cx, |editor, window, cx| {
 9422        assert_text_with_selections(
 9423            editor,
 9424            indoc! {r#"
 9425                use mod1::mod2::{mod3, mod4};
 9426
 9427                fn fn_1(param1: bool, param2: &str) {
 9428                    let var1 = "hˇello world";
 9429                }
 9430            "#},
 9431            cx,
 9432        );
 9433        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9434        assert_text_with_selections(
 9435            editor,
 9436            indoc! {r#"
 9437                use mod1::mod2::{mod3, mod4};
 9438
 9439                fn fn_1(param1: bool, param2: &str) {
 9440                    let var1 = "«ˇhello» world";
 9441                }
 9442            "#},
 9443            cx,
 9444        );
 9445    });
 9446
 9447    // Test 2: Partial selection within a word
 9448    editor.update_in(cx, |editor, window, cx| {
 9449        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9450            s.select_display_ranges([
 9451                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9452            ]);
 9453        });
 9454    });
 9455    editor.update_in(cx, |editor, window, cx| {
 9456        assert_text_with_selections(
 9457            editor,
 9458            indoc! {r#"
 9459                use mod1::mod2::{mod3, mod4};
 9460
 9461                fn fn_1(param1: bool, param2: &str) {
 9462                    let var1 = "h«elˇ»lo world";
 9463                }
 9464            "#},
 9465            cx,
 9466        );
 9467        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9468        assert_text_with_selections(
 9469            editor,
 9470            indoc! {r#"
 9471                use mod1::mod2::{mod3, mod4};
 9472
 9473                fn fn_1(param1: bool, param2: &str) {
 9474                    let var1 = "«ˇhello» world";
 9475                }
 9476            "#},
 9477            cx,
 9478        );
 9479    });
 9480
 9481    // Test 3: Complete word already selected
 9482    editor.update_in(cx, |editor, window, cx| {
 9483        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9484            s.select_display_ranges([
 9485                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9486            ]);
 9487        });
 9488    });
 9489    editor.update_in(cx, |editor, window, cx| {
 9490        assert_text_with_selections(
 9491            editor,
 9492            indoc! {r#"
 9493                use mod1::mod2::{mod3, mod4};
 9494
 9495                fn fn_1(param1: bool, param2: &str) {
 9496                    let var1 = "«helloˇ» world";
 9497                }
 9498            "#},
 9499            cx,
 9500        );
 9501        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9502        assert_text_with_selections(
 9503            editor,
 9504            indoc! {r#"
 9505                use mod1::mod2::{mod3, mod4};
 9506
 9507                fn fn_1(param1: bool, param2: &str) {
 9508                    let var1 = "«hello worldˇ»";
 9509                }
 9510            "#},
 9511            cx,
 9512        );
 9513    });
 9514
 9515    // Test 4: Selection spanning across words
 9516    editor.update_in(cx, |editor, window, cx| {
 9517        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9518            s.select_display_ranges([
 9519                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9520            ]);
 9521        });
 9522    });
 9523    editor.update_in(cx, |editor, window, cx| {
 9524        assert_text_with_selections(
 9525            editor,
 9526            indoc! {r#"
 9527                use mod1::mod2::{mod3, mod4};
 9528
 9529                fn fn_1(param1: bool, param2: &str) {
 9530                    let var1 = "hel«lo woˇ»rld";
 9531                }
 9532            "#},
 9533            cx,
 9534        );
 9535        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9536        assert_text_with_selections(
 9537            editor,
 9538            indoc! {r#"
 9539                use mod1::mod2::{mod3, mod4};
 9540
 9541                fn fn_1(param1: bool, param2: &str) {
 9542                    let var1 = "«ˇhello world»";
 9543                }
 9544            "#},
 9545            cx,
 9546        );
 9547    });
 9548
 9549    // Test 5: Expansion beyond string
 9550    editor.update_in(cx, |editor, window, cx| {
 9551        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9552        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9553        assert_text_with_selections(
 9554            editor,
 9555            indoc! {r#"
 9556                use mod1::mod2::{mod3, mod4};
 9557
 9558                fn fn_1(param1: bool, param2: &str) {
 9559                    «ˇlet var1 = "hello world";»
 9560                }
 9561            "#},
 9562            cx,
 9563        );
 9564    });
 9565}
 9566
 9567#[gpui::test]
 9568async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9569    init_test(cx, |_| {});
 9570
 9571    let mut cx = EditorTestContext::new(cx).await;
 9572
 9573    let language = Arc::new(Language::new(
 9574        LanguageConfig::default(),
 9575        Some(tree_sitter_rust::LANGUAGE.into()),
 9576    ));
 9577
 9578    cx.update_buffer(|buffer, cx| {
 9579        buffer.set_language(Some(language), cx);
 9580    });
 9581
 9582    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9583    cx.update_editor(|editor, window, cx| {
 9584        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9585    });
 9586
 9587    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9588
 9589    cx.set_state(indoc! { r#"fn a() {
 9590          // what
 9591          // a
 9592          // ˇlong
 9593          // method
 9594          // I
 9595          // sure
 9596          // hope
 9597          // it
 9598          // works
 9599    }"# });
 9600
 9601    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9602    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9603    cx.update(|_, cx| {
 9604        multi_buffer.update(cx, |multi_buffer, cx| {
 9605            multi_buffer.set_excerpts_for_path(
 9606                PathKey::for_buffer(&buffer, cx),
 9607                buffer,
 9608                [Point::new(1, 0)..Point::new(1, 0)],
 9609                3,
 9610                cx,
 9611            );
 9612        });
 9613    });
 9614
 9615    let editor2 = cx.new_window_entity(|window, cx| {
 9616        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9617    });
 9618
 9619    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9620    cx.update_editor(|editor, window, cx| {
 9621        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9622            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9623        })
 9624    });
 9625
 9626    cx.assert_editor_state(indoc! { "
 9627        fn a() {
 9628              // what
 9629              // a
 9630        ˇ      // long
 9631              // method"});
 9632
 9633    cx.update_editor(|editor, window, cx| {
 9634        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9635    });
 9636
 9637    // Although we could potentially make the action work when the syntax node
 9638    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9639    // did. Maybe we could also expand the excerpt to contain the range?
 9640    cx.assert_editor_state(indoc! { "
 9641        fn a() {
 9642              // what
 9643              // a
 9644        ˇ      // long
 9645              // method"});
 9646}
 9647
 9648#[gpui::test]
 9649async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9650    init_test(cx, |_| {});
 9651
 9652    let base_text = r#"
 9653        impl A {
 9654            // this is an uncommitted comment
 9655
 9656            fn b() {
 9657                c();
 9658            }
 9659
 9660            // this is another uncommitted comment
 9661
 9662            fn d() {
 9663                // e
 9664                // f
 9665            }
 9666        }
 9667
 9668        fn g() {
 9669            // h
 9670        }
 9671    "#
 9672    .unindent();
 9673
 9674    let text = r#"
 9675        ˇimpl A {
 9676
 9677            fn b() {
 9678                c();
 9679            }
 9680
 9681            fn d() {
 9682                // e
 9683                // f
 9684            }
 9685        }
 9686
 9687        fn g() {
 9688            // h
 9689        }
 9690    "#
 9691    .unindent();
 9692
 9693    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9694    cx.set_state(&text);
 9695    cx.set_head_text(&base_text);
 9696    cx.update_editor(|editor, window, cx| {
 9697        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9698    });
 9699
 9700    cx.assert_state_with_diff(
 9701        "
 9702        ˇimpl A {
 9703      -     // this is an uncommitted comment
 9704
 9705            fn b() {
 9706                c();
 9707            }
 9708
 9709      -     // this is another uncommitted comment
 9710      -
 9711            fn d() {
 9712                // e
 9713                // f
 9714            }
 9715        }
 9716
 9717        fn g() {
 9718            // h
 9719        }
 9720    "
 9721        .unindent(),
 9722    );
 9723
 9724    let expected_display_text = "
 9725        impl A {
 9726            // this is an uncommitted comment
 9727
 9728            fn b() {
 9729 9730            }
 9731
 9732            // this is another uncommitted comment
 9733
 9734            fn d() {
 9735 9736            }
 9737        }
 9738
 9739        fn g() {
 9740 9741        }
 9742        "
 9743    .unindent();
 9744
 9745    cx.update_editor(|editor, window, cx| {
 9746        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9747        assert_eq!(editor.display_text(cx), expected_display_text);
 9748    });
 9749}
 9750
 9751#[gpui::test]
 9752async fn test_autoindent(cx: &mut TestAppContext) {
 9753    init_test(cx, |_| {});
 9754
 9755    let language = Arc::new(
 9756        Language::new(
 9757            LanguageConfig {
 9758                brackets: BracketPairConfig {
 9759                    pairs: vec![
 9760                        BracketPair {
 9761                            start: "{".to_string(),
 9762                            end: "}".to_string(),
 9763                            close: false,
 9764                            surround: false,
 9765                            newline: true,
 9766                        },
 9767                        BracketPair {
 9768                            start: "(".to_string(),
 9769                            end: ")".to_string(),
 9770                            close: false,
 9771                            surround: false,
 9772                            newline: true,
 9773                        },
 9774                    ],
 9775                    ..Default::default()
 9776                },
 9777                ..Default::default()
 9778            },
 9779            Some(tree_sitter_rust::LANGUAGE.into()),
 9780        )
 9781        .with_indents_query(
 9782            r#"
 9783                (_ "(" ")" @end) @indent
 9784                (_ "{" "}" @end) @indent
 9785            "#,
 9786        )
 9787        .unwrap(),
 9788    );
 9789
 9790    let text = "fn a() {}";
 9791
 9792    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9793    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9794    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9795    editor
 9796        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9797        .await;
 9798
 9799    editor.update_in(cx, |editor, window, cx| {
 9800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9801            s.select_ranges([
 9802                MultiBufferOffset(5)..MultiBufferOffset(5),
 9803                MultiBufferOffset(8)..MultiBufferOffset(8),
 9804                MultiBufferOffset(9)..MultiBufferOffset(9),
 9805            ])
 9806        });
 9807        editor.newline(&Newline, window, cx);
 9808        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9809        assert_eq!(
 9810            editor.selections.ranges(&editor.display_snapshot(cx)),
 9811            &[
 9812                Point::new(1, 4)..Point::new(1, 4),
 9813                Point::new(3, 4)..Point::new(3, 4),
 9814                Point::new(5, 0)..Point::new(5, 0)
 9815            ]
 9816        );
 9817    });
 9818}
 9819
 9820#[gpui::test]
 9821async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9822    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9823
 9824    let language = Arc::new(
 9825        Language::new(
 9826            LanguageConfig {
 9827                brackets: BracketPairConfig {
 9828                    pairs: vec![
 9829                        BracketPair {
 9830                            start: "{".to_string(),
 9831                            end: "}".to_string(),
 9832                            close: false,
 9833                            surround: false,
 9834                            newline: true,
 9835                        },
 9836                        BracketPair {
 9837                            start: "(".to_string(),
 9838                            end: ")".to_string(),
 9839                            close: false,
 9840                            surround: false,
 9841                            newline: true,
 9842                        },
 9843                    ],
 9844                    ..Default::default()
 9845                },
 9846                ..Default::default()
 9847            },
 9848            Some(tree_sitter_rust::LANGUAGE.into()),
 9849        )
 9850        .with_indents_query(
 9851            r#"
 9852                (_ "(" ")" @end) @indent
 9853                (_ "{" "}" @end) @indent
 9854            "#,
 9855        )
 9856        .unwrap(),
 9857    );
 9858
 9859    let text = "fn a() {}";
 9860
 9861    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9862    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9863    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9864    editor
 9865        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9866        .await;
 9867
 9868    editor.update_in(cx, |editor, window, cx| {
 9869        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9870            s.select_ranges([
 9871                MultiBufferOffset(5)..MultiBufferOffset(5),
 9872                MultiBufferOffset(8)..MultiBufferOffset(8),
 9873                MultiBufferOffset(9)..MultiBufferOffset(9),
 9874            ])
 9875        });
 9876        editor.newline(&Newline, window, cx);
 9877        assert_eq!(
 9878            editor.text(cx),
 9879            indoc!(
 9880                "
 9881                fn a(
 9882
 9883                ) {
 9884
 9885                }
 9886                "
 9887            )
 9888        );
 9889        assert_eq!(
 9890            editor.selections.ranges(&editor.display_snapshot(cx)),
 9891            &[
 9892                Point::new(1, 0)..Point::new(1, 0),
 9893                Point::new(3, 0)..Point::new(3, 0),
 9894                Point::new(5, 0)..Point::new(5, 0)
 9895            ]
 9896        );
 9897    });
 9898}
 9899
 9900#[gpui::test]
 9901async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9902    init_test(cx, |settings| {
 9903        settings.defaults.auto_indent = Some(true);
 9904        settings.languages.0.insert(
 9905            "python".into(),
 9906            LanguageSettingsContent {
 9907                auto_indent: Some(false),
 9908                ..Default::default()
 9909            },
 9910        );
 9911    });
 9912
 9913    let mut cx = EditorTestContext::new(cx).await;
 9914
 9915    let injected_language = Arc::new(
 9916        Language::new(
 9917            LanguageConfig {
 9918                brackets: BracketPairConfig {
 9919                    pairs: vec![
 9920                        BracketPair {
 9921                            start: "{".to_string(),
 9922                            end: "}".to_string(),
 9923                            close: false,
 9924                            surround: false,
 9925                            newline: true,
 9926                        },
 9927                        BracketPair {
 9928                            start: "(".to_string(),
 9929                            end: ")".to_string(),
 9930                            close: true,
 9931                            surround: false,
 9932                            newline: true,
 9933                        },
 9934                    ],
 9935                    ..Default::default()
 9936                },
 9937                name: "python".into(),
 9938                ..Default::default()
 9939            },
 9940            Some(tree_sitter_python::LANGUAGE.into()),
 9941        )
 9942        .with_indents_query(
 9943            r#"
 9944                (_ "(" ")" @end) @indent
 9945                (_ "{" "}" @end) @indent
 9946            "#,
 9947        )
 9948        .unwrap(),
 9949    );
 9950
 9951    let language = Arc::new(
 9952        Language::new(
 9953            LanguageConfig {
 9954                brackets: BracketPairConfig {
 9955                    pairs: vec![
 9956                        BracketPair {
 9957                            start: "{".to_string(),
 9958                            end: "}".to_string(),
 9959                            close: false,
 9960                            surround: false,
 9961                            newline: true,
 9962                        },
 9963                        BracketPair {
 9964                            start: "(".to_string(),
 9965                            end: ")".to_string(),
 9966                            close: true,
 9967                            surround: false,
 9968                            newline: true,
 9969                        },
 9970                    ],
 9971                    ..Default::default()
 9972                },
 9973                name: LanguageName::new("rust"),
 9974                ..Default::default()
 9975            },
 9976            Some(tree_sitter_rust::LANGUAGE.into()),
 9977        )
 9978        .with_indents_query(
 9979            r#"
 9980                (_ "(" ")" @end) @indent
 9981                (_ "{" "}" @end) @indent
 9982            "#,
 9983        )
 9984        .unwrap()
 9985        .with_injection_query(
 9986            r#"
 9987            (macro_invocation
 9988                macro: (identifier) @_macro_name
 9989                (token_tree) @injection.content
 9990                (#set! injection.language "python"))
 9991           "#,
 9992        )
 9993        .unwrap(),
 9994    );
 9995
 9996    cx.language_registry().add(injected_language);
 9997    cx.language_registry().add(language.clone());
 9998
 9999    cx.update_buffer(|buffer, cx| {
10000        buffer.set_language(Some(language), cx);
10001    });
10002
10003    cx.set_state(r#"struct A {ˇ}"#);
10004
10005    cx.update_editor(|editor, window, cx| {
10006        editor.newline(&Default::default(), window, cx);
10007    });
10008
10009    cx.assert_editor_state(indoc!(
10010        "struct A {
10011            ˇ
10012        }"
10013    ));
10014
10015    cx.set_state(r#"select_biased!(ˇ)"#);
10016
10017    cx.update_editor(|editor, window, cx| {
10018        editor.newline(&Default::default(), window, cx);
10019        editor.handle_input("def ", window, cx);
10020        editor.handle_input("(", window, cx);
10021        editor.newline(&Default::default(), window, cx);
10022        editor.handle_input("a", window, cx);
10023    });
10024
10025    cx.assert_editor_state(indoc!(
10026        "select_biased!(
10027        def (
1002810029        )
10030        )"
10031    ));
10032}
10033
10034#[gpui::test]
10035async fn test_autoindent_selections(cx: &mut TestAppContext) {
10036    init_test(cx, |_| {});
10037
10038    {
10039        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10040        cx.set_state(indoc! {"
10041            impl A {
10042
10043                fn b() {}
10044
10045            «fn c() {
10046
10047            }ˇ»
10048            }
10049        "});
10050
10051        cx.update_editor(|editor, window, cx| {
10052            editor.autoindent(&Default::default(), window, cx);
10053        });
10054
10055        cx.assert_editor_state(indoc! {"
10056            impl A {
10057
10058                fn b() {}
10059
10060                «fn c() {
10061
10062                }ˇ»
10063            }
10064        "});
10065    }
10066
10067    {
10068        let mut cx = EditorTestContext::new_multibuffer(
10069            cx,
10070            [indoc! { "
10071                impl A {
10072                «
10073                // a
10074                fn b(){}
10075                »
10076                «
10077                    }
10078                    fn c(){}
10079                »
10080            "}],
10081        );
10082
10083        let buffer = cx.update_editor(|editor, _, cx| {
10084            let buffer = editor.buffer().update(cx, |buffer, _| {
10085                buffer.all_buffers().iter().next().unwrap().clone()
10086            });
10087            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10088            buffer
10089        });
10090
10091        cx.run_until_parked();
10092        cx.update_editor(|editor, window, cx| {
10093            editor.select_all(&Default::default(), window, cx);
10094            editor.autoindent(&Default::default(), window, cx)
10095        });
10096        cx.run_until_parked();
10097
10098        cx.update(|_, cx| {
10099            assert_eq!(
10100                buffer.read(cx).text(),
10101                indoc! { "
10102                    impl A {
10103
10104                        // a
10105                        fn b(){}
10106
10107
10108                    }
10109                    fn c(){}
10110
10111                " }
10112            )
10113        });
10114    }
10115}
10116
10117#[gpui::test]
10118async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10119    init_test(cx, |_| {});
10120
10121    let mut cx = EditorTestContext::new(cx).await;
10122
10123    let language = Arc::new(Language::new(
10124        LanguageConfig {
10125            brackets: BracketPairConfig {
10126                pairs: vec![
10127                    BracketPair {
10128                        start: "{".to_string(),
10129                        end: "}".to_string(),
10130                        close: true,
10131                        surround: true,
10132                        newline: true,
10133                    },
10134                    BracketPair {
10135                        start: "(".to_string(),
10136                        end: ")".to_string(),
10137                        close: true,
10138                        surround: true,
10139                        newline: true,
10140                    },
10141                    BracketPair {
10142                        start: "/*".to_string(),
10143                        end: " */".to_string(),
10144                        close: true,
10145                        surround: true,
10146                        newline: true,
10147                    },
10148                    BracketPair {
10149                        start: "[".to_string(),
10150                        end: "]".to_string(),
10151                        close: false,
10152                        surround: false,
10153                        newline: true,
10154                    },
10155                    BracketPair {
10156                        start: "\"".to_string(),
10157                        end: "\"".to_string(),
10158                        close: true,
10159                        surround: true,
10160                        newline: false,
10161                    },
10162                    BracketPair {
10163                        start: "<".to_string(),
10164                        end: ">".to_string(),
10165                        close: false,
10166                        surround: true,
10167                        newline: true,
10168                    },
10169                ],
10170                ..Default::default()
10171            },
10172            autoclose_before: "})]".to_string(),
10173            ..Default::default()
10174        },
10175        Some(tree_sitter_rust::LANGUAGE.into()),
10176    ));
10177
10178    cx.language_registry().add(language.clone());
10179    cx.update_buffer(|buffer, cx| {
10180        buffer.set_language(Some(language), cx);
10181    });
10182
10183    cx.set_state(
10184        &r#"
10185            🏀ˇ
10186            εˇ
10187            ❤️ˇ
10188        "#
10189        .unindent(),
10190    );
10191
10192    // autoclose multiple nested brackets at multiple cursors
10193    cx.update_editor(|editor, window, cx| {
10194        editor.handle_input("{", window, cx);
10195        editor.handle_input("{", window, cx);
10196        editor.handle_input("{", window, cx);
10197    });
10198    cx.assert_editor_state(
10199        &"
10200            🏀{{{ˇ}}}
10201            ε{{{ˇ}}}
10202            ❤️{{{ˇ}}}
10203        "
10204        .unindent(),
10205    );
10206
10207    // insert a different closing bracket
10208    cx.update_editor(|editor, window, cx| {
10209        editor.handle_input(")", window, cx);
10210    });
10211    cx.assert_editor_state(
10212        &"
10213            🏀{{{)ˇ}}}
10214            ε{{{)ˇ}}}
10215            ❤️{{{)ˇ}}}
10216        "
10217        .unindent(),
10218    );
10219
10220    // skip over the auto-closed brackets when typing a closing bracket
10221    cx.update_editor(|editor, window, cx| {
10222        editor.move_right(&MoveRight, window, cx);
10223        editor.handle_input("}", window, cx);
10224        editor.handle_input("}", window, cx);
10225        editor.handle_input("}", window, cx);
10226    });
10227    cx.assert_editor_state(
10228        &"
10229            🏀{{{)}}}}ˇ
10230            ε{{{)}}}}ˇ
10231            ❤️{{{)}}}}ˇ
10232        "
10233        .unindent(),
10234    );
10235
10236    // autoclose multi-character pairs
10237    cx.set_state(
10238        &"
10239            ˇ
10240            ˇ
10241        "
10242        .unindent(),
10243    );
10244    cx.update_editor(|editor, window, cx| {
10245        editor.handle_input("/", window, cx);
10246        editor.handle_input("*", window, cx);
10247    });
10248    cx.assert_editor_state(
10249        &"
10250            /*ˇ */
10251            /*ˇ */
10252        "
10253        .unindent(),
10254    );
10255
10256    // one cursor autocloses a multi-character pair, one cursor
10257    // does not autoclose.
10258    cx.set_state(
10259        &"
1026010261            ˇ
10262        "
10263        .unindent(),
10264    );
10265    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10266    cx.assert_editor_state(
10267        &"
10268            /*ˇ */
1026910270        "
10271        .unindent(),
10272    );
10273
10274    // Don't autoclose if the next character isn't whitespace and isn't
10275    // listed in the language's "autoclose_before" section.
10276    cx.set_state("ˇa b");
10277    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10278    cx.assert_editor_state("{ˇa b");
10279
10280    // Don't autoclose if `close` is false for the bracket pair
10281    cx.set_state("ˇ");
10282    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10283    cx.assert_editor_state("");
10284
10285    // Surround with brackets if text is selected
10286    cx.set_state("«aˇ» b");
10287    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10288    cx.assert_editor_state("{«aˇ»} b");
10289
10290    // Autoclose when not immediately after a word character
10291    cx.set_state("a ˇ");
10292    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10293    cx.assert_editor_state("a \"ˇ\"");
10294
10295    // Autoclose pair where the start and end characters are the same
10296    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10297    cx.assert_editor_state("a \"\"ˇ");
10298
10299    // Don't autoclose when immediately after a word character
10300    cx.set_state("");
10301    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10302    cx.assert_editor_state("a\"ˇ");
10303
10304    // Do autoclose when after a non-word character
10305    cx.set_state("");
10306    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10307    cx.assert_editor_state("{\"ˇ\"");
10308
10309    // Non identical pairs autoclose regardless of preceding character
10310    cx.set_state("");
10311    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10312    cx.assert_editor_state("a{ˇ}");
10313
10314    // Don't autoclose pair if autoclose is disabled
10315    cx.set_state("ˇ");
10316    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10317    cx.assert_editor_state("");
10318
10319    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10320    cx.set_state("«aˇ» b");
10321    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10322    cx.assert_editor_state("<«aˇ»> b");
10323}
10324
10325#[gpui::test]
10326async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10327    init_test(cx, |settings| {
10328        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10329    });
10330
10331    let mut cx = EditorTestContext::new(cx).await;
10332
10333    let language = Arc::new(Language::new(
10334        LanguageConfig {
10335            brackets: BracketPairConfig {
10336                pairs: vec![
10337                    BracketPair {
10338                        start: "{".to_string(),
10339                        end: "}".to_string(),
10340                        close: true,
10341                        surround: true,
10342                        newline: true,
10343                    },
10344                    BracketPair {
10345                        start: "(".to_string(),
10346                        end: ")".to_string(),
10347                        close: true,
10348                        surround: true,
10349                        newline: true,
10350                    },
10351                    BracketPair {
10352                        start: "[".to_string(),
10353                        end: "]".to_string(),
10354                        close: false,
10355                        surround: false,
10356                        newline: true,
10357                    },
10358                ],
10359                ..Default::default()
10360            },
10361            autoclose_before: "})]".to_string(),
10362            ..Default::default()
10363        },
10364        Some(tree_sitter_rust::LANGUAGE.into()),
10365    ));
10366
10367    cx.language_registry().add(language.clone());
10368    cx.update_buffer(|buffer, cx| {
10369        buffer.set_language(Some(language), cx);
10370    });
10371
10372    cx.set_state(
10373        &"
10374            ˇ
10375            ˇ
10376            ˇ
10377        "
10378        .unindent(),
10379    );
10380
10381    // ensure only matching closing brackets are skipped over
10382    cx.update_editor(|editor, window, cx| {
10383        editor.handle_input("}", window, cx);
10384        editor.move_left(&MoveLeft, window, cx);
10385        editor.handle_input(")", window, cx);
10386        editor.move_left(&MoveLeft, window, cx);
10387    });
10388    cx.assert_editor_state(
10389        &"
10390            ˇ)}
10391            ˇ)}
10392            ˇ)}
10393        "
10394        .unindent(),
10395    );
10396
10397    // skip-over closing brackets at multiple cursors
10398    cx.update_editor(|editor, window, cx| {
10399        editor.handle_input(")", window, cx);
10400        editor.handle_input("}", window, cx);
10401    });
10402    cx.assert_editor_state(
10403        &"
10404            )}ˇ
10405            )}ˇ
10406            )}ˇ
10407        "
10408        .unindent(),
10409    );
10410
10411    // ignore non-close brackets
10412    cx.update_editor(|editor, window, cx| {
10413        editor.handle_input("]", window, cx);
10414        editor.move_left(&MoveLeft, window, cx);
10415        editor.handle_input("]", window, cx);
10416    });
10417    cx.assert_editor_state(
10418        &"
10419            )}]ˇ]
10420            )}]ˇ]
10421            )}]ˇ]
10422        "
10423        .unindent(),
10424    );
10425}
10426
10427#[gpui::test]
10428async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10429    init_test(cx, |_| {});
10430
10431    let mut cx = EditorTestContext::new(cx).await;
10432
10433    let html_language = Arc::new(
10434        Language::new(
10435            LanguageConfig {
10436                name: "HTML".into(),
10437                brackets: BracketPairConfig {
10438                    pairs: vec![
10439                        BracketPair {
10440                            start: "<".into(),
10441                            end: ">".into(),
10442                            close: true,
10443                            ..Default::default()
10444                        },
10445                        BracketPair {
10446                            start: "{".into(),
10447                            end: "}".into(),
10448                            close: true,
10449                            ..Default::default()
10450                        },
10451                        BracketPair {
10452                            start: "(".into(),
10453                            end: ")".into(),
10454                            close: true,
10455                            ..Default::default()
10456                        },
10457                    ],
10458                    ..Default::default()
10459                },
10460                autoclose_before: "})]>".into(),
10461                ..Default::default()
10462            },
10463            Some(tree_sitter_html::LANGUAGE.into()),
10464        )
10465        .with_injection_query(
10466            r#"
10467            (script_element
10468                (raw_text) @injection.content
10469                (#set! injection.language "javascript"))
10470            "#,
10471        )
10472        .unwrap(),
10473    );
10474
10475    let javascript_language = Arc::new(Language::new(
10476        LanguageConfig {
10477            name: "JavaScript".into(),
10478            brackets: BracketPairConfig {
10479                pairs: vec![
10480                    BracketPair {
10481                        start: "/*".into(),
10482                        end: " */".into(),
10483                        close: true,
10484                        ..Default::default()
10485                    },
10486                    BracketPair {
10487                        start: "{".into(),
10488                        end: "}".into(),
10489                        close: true,
10490                        ..Default::default()
10491                    },
10492                    BracketPair {
10493                        start: "(".into(),
10494                        end: ")".into(),
10495                        close: true,
10496                        ..Default::default()
10497                    },
10498                ],
10499                ..Default::default()
10500            },
10501            autoclose_before: "})]>".into(),
10502            ..Default::default()
10503        },
10504        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10505    ));
10506
10507    cx.language_registry().add(html_language.clone());
10508    cx.language_registry().add(javascript_language);
10509    cx.executor().run_until_parked();
10510
10511    cx.update_buffer(|buffer, cx| {
10512        buffer.set_language(Some(html_language), cx);
10513    });
10514
10515    cx.set_state(
10516        &r#"
10517            <body>ˇ
10518                <script>
10519                    var x = 1;ˇ
10520                </script>
10521            </body>ˇ
10522        "#
10523        .unindent(),
10524    );
10525
10526    // Precondition: different languages are active at different locations.
10527    cx.update_editor(|editor, window, cx| {
10528        let snapshot = editor.snapshot(window, cx);
10529        let cursors = editor
10530            .selections
10531            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10532        let languages = cursors
10533            .iter()
10534            .map(|c| snapshot.language_at(c.start).unwrap().name())
10535            .collect::<Vec<_>>();
10536        assert_eq!(
10537            languages,
10538            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10539        );
10540    });
10541
10542    // Angle brackets autoclose in HTML, but not JavaScript.
10543    cx.update_editor(|editor, window, cx| {
10544        editor.handle_input("<", window, cx);
10545        editor.handle_input("a", window, cx);
10546    });
10547    cx.assert_editor_state(
10548        &r#"
10549            <body><aˇ>
10550                <script>
10551                    var x = 1;<aˇ
10552                </script>
10553            </body><aˇ>
10554        "#
10555        .unindent(),
10556    );
10557
10558    // Curly braces and parens autoclose in both HTML and JavaScript.
10559    cx.update_editor(|editor, window, cx| {
10560        editor.handle_input(" b=", window, cx);
10561        editor.handle_input("{", window, cx);
10562        editor.handle_input("c", window, cx);
10563        editor.handle_input("(", window, cx);
10564    });
10565    cx.assert_editor_state(
10566        &r#"
10567            <body><a b={c(ˇ)}>
10568                <script>
10569                    var x = 1;<a b={c(ˇ)}
10570                </script>
10571            </body><a b={c(ˇ)}>
10572        "#
10573        .unindent(),
10574    );
10575
10576    // Brackets that were already autoclosed are skipped.
10577    cx.update_editor(|editor, window, cx| {
10578        editor.handle_input(")", window, cx);
10579        editor.handle_input("d", window, cx);
10580        editor.handle_input("}", window, cx);
10581    });
10582    cx.assert_editor_state(
10583        &r#"
10584            <body><a b={c()d}ˇ>
10585                <script>
10586                    var x = 1;<a b={c()d}ˇ
10587                </script>
10588            </body><a b={c()d}ˇ>
10589        "#
10590        .unindent(),
10591    );
10592    cx.update_editor(|editor, window, cx| {
10593        editor.handle_input(">", window, cx);
10594    });
10595    cx.assert_editor_state(
10596        &r#"
10597            <body><a b={c()d}>ˇ
10598                <script>
10599                    var x = 1;<a b={c()d}>ˇ
10600                </script>
10601            </body><a b={c()d}>ˇ
10602        "#
10603        .unindent(),
10604    );
10605
10606    // Reset
10607    cx.set_state(
10608        &r#"
10609            <body>ˇ
10610                <script>
10611                    var x = 1;ˇ
10612                </script>
10613            </body>ˇ
10614        "#
10615        .unindent(),
10616    );
10617
10618    cx.update_editor(|editor, window, cx| {
10619        editor.handle_input("<", window, cx);
10620    });
10621    cx.assert_editor_state(
10622        &r#"
10623            <body><ˇ>
10624                <script>
10625                    var x = 1;<ˇ
10626                </script>
10627            </body><ˇ>
10628        "#
10629        .unindent(),
10630    );
10631
10632    // When backspacing, the closing angle brackets are removed.
10633    cx.update_editor(|editor, window, cx| {
10634        editor.backspace(&Backspace, window, cx);
10635    });
10636    cx.assert_editor_state(
10637        &r#"
10638            <body>ˇ
10639                <script>
10640                    var x = 1;ˇ
10641                </script>
10642            </body>ˇ
10643        "#
10644        .unindent(),
10645    );
10646
10647    // Block comments autoclose in JavaScript, but not HTML.
10648    cx.update_editor(|editor, window, cx| {
10649        editor.handle_input("/", window, cx);
10650        editor.handle_input("*", window, cx);
10651    });
10652    cx.assert_editor_state(
10653        &r#"
10654            <body>/*ˇ
10655                <script>
10656                    var x = 1;/*ˇ */
10657                </script>
10658            </body>/*ˇ
10659        "#
10660        .unindent(),
10661    );
10662}
10663
10664#[gpui::test]
10665async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10666    init_test(cx, |_| {});
10667
10668    let mut cx = EditorTestContext::new(cx).await;
10669
10670    let rust_language = Arc::new(
10671        Language::new(
10672            LanguageConfig {
10673                name: "Rust".into(),
10674                brackets: serde_json::from_value(json!([
10675                    { "start": "{", "end": "}", "close": true, "newline": true },
10676                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10677                ]))
10678                .unwrap(),
10679                autoclose_before: "})]>".into(),
10680                ..Default::default()
10681            },
10682            Some(tree_sitter_rust::LANGUAGE.into()),
10683        )
10684        .with_override_query("(string_literal) @string")
10685        .unwrap(),
10686    );
10687
10688    cx.language_registry().add(rust_language.clone());
10689    cx.update_buffer(|buffer, cx| {
10690        buffer.set_language(Some(rust_language), cx);
10691    });
10692
10693    cx.set_state(
10694        &r#"
10695            let x = ˇ
10696        "#
10697        .unindent(),
10698    );
10699
10700    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10701    cx.update_editor(|editor, window, cx| {
10702        editor.handle_input("\"", window, cx);
10703    });
10704    cx.assert_editor_state(
10705        &r#"
10706            let x = "ˇ"
10707        "#
10708        .unindent(),
10709    );
10710
10711    // Inserting another quotation mark. The cursor moves across the existing
10712    // automatically-inserted quotation mark.
10713    cx.update_editor(|editor, window, cx| {
10714        editor.handle_input("\"", window, cx);
10715    });
10716    cx.assert_editor_state(
10717        &r#"
10718            let x = ""ˇ
10719        "#
10720        .unindent(),
10721    );
10722
10723    // Reset
10724    cx.set_state(
10725        &r#"
10726            let x = ˇ
10727        "#
10728        .unindent(),
10729    );
10730
10731    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10732    cx.update_editor(|editor, window, cx| {
10733        editor.handle_input("\"", window, cx);
10734        editor.handle_input(" ", window, cx);
10735        editor.move_left(&Default::default(), window, cx);
10736        editor.handle_input("\\", window, cx);
10737        editor.handle_input("\"", window, cx);
10738    });
10739    cx.assert_editor_state(
10740        &r#"
10741            let x = "\"ˇ "
10742        "#
10743        .unindent(),
10744    );
10745
10746    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10747    // mark. Nothing is inserted.
10748    cx.update_editor(|editor, window, cx| {
10749        editor.move_right(&Default::default(), window, cx);
10750        editor.handle_input("\"", window, cx);
10751    });
10752    cx.assert_editor_state(
10753        &r#"
10754            let x = "\" "ˇ
10755        "#
10756        .unindent(),
10757    );
10758}
10759
10760#[gpui::test]
10761async fn test_surround_with_pair(cx: &mut TestAppContext) {
10762    init_test(cx, |_| {});
10763
10764    let language = Arc::new(Language::new(
10765        LanguageConfig {
10766            brackets: BracketPairConfig {
10767                pairs: vec![
10768                    BracketPair {
10769                        start: "{".to_string(),
10770                        end: "}".to_string(),
10771                        close: true,
10772                        surround: true,
10773                        newline: true,
10774                    },
10775                    BracketPair {
10776                        start: "/* ".to_string(),
10777                        end: "*/".to_string(),
10778                        close: true,
10779                        surround: true,
10780                        ..Default::default()
10781                    },
10782                ],
10783                ..Default::default()
10784            },
10785            ..Default::default()
10786        },
10787        Some(tree_sitter_rust::LANGUAGE.into()),
10788    ));
10789
10790    let text = r#"
10791        a
10792        b
10793        c
10794    "#
10795    .unindent();
10796
10797    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10798    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10799    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10800    editor
10801        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10802        .await;
10803
10804    editor.update_in(cx, |editor, window, cx| {
10805        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10806            s.select_display_ranges([
10807                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10808                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10809                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10810            ])
10811        });
10812
10813        editor.handle_input("{", window, cx);
10814        editor.handle_input("{", window, cx);
10815        editor.handle_input("{", window, cx);
10816        assert_eq!(
10817            editor.text(cx),
10818            "
10819                {{{a}}}
10820                {{{b}}}
10821                {{{c}}}
10822            "
10823            .unindent()
10824        );
10825        assert_eq!(
10826            display_ranges(editor, cx),
10827            [
10828                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10829                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10830                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10831            ]
10832        );
10833
10834        editor.undo(&Undo, window, cx);
10835        editor.undo(&Undo, window, cx);
10836        editor.undo(&Undo, window, cx);
10837        assert_eq!(
10838            editor.text(cx),
10839            "
10840                a
10841                b
10842                c
10843            "
10844            .unindent()
10845        );
10846        assert_eq!(
10847            display_ranges(editor, cx),
10848            [
10849                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10851                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10852            ]
10853        );
10854
10855        // Ensure inserting the first character of a multi-byte bracket pair
10856        // doesn't surround the selections with the bracket.
10857        editor.handle_input("/", window, cx);
10858        assert_eq!(
10859            editor.text(cx),
10860            "
10861                /
10862                /
10863                /
10864            "
10865            .unindent()
10866        );
10867        assert_eq!(
10868            display_ranges(editor, cx),
10869            [
10870                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10871                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10872                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10873            ]
10874        );
10875
10876        editor.undo(&Undo, window, cx);
10877        assert_eq!(
10878            editor.text(cx),
10879            "
10880                a
10881                b
10882                c
10883            "
10884            .unindent()
10885        );
10886        assert_eq!(
10887            display_ranges(editor, cx),
10888            [
10889                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10890                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10891                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10892            ]
10893        );
10894
10895        // Ensure inserting the last character of a multi-byte bracket pair
10896        // doesn't surround the selections with the bracket.
10897        editor.handle_input("*", window, cx);
10898        assert_eq!(
10899            editor.text(cx),
10900            "
10901                *
10902                *
10903                *
10904            "
10905            .unindent()
10906        );
10907        assert_eq!(
10908            display_ranges(editor, cx),
10909            [
10910                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10911                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10912                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10913            ]
10914        );
10915    });
10916}
10917
10918#[gpui::test]
10919async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10920    init_test(cx, |_| {});
10921
10922    let language = Arc::new(Language::new(
10923        LanguageConfig {
10924            brackets: BracketPairConfig {
10925                pairs: vec![BracketPair {
10926                    start: "{".to_string(),
10927                    end: "}".to_string(),
10928                    close: true,
10929                    surround: true,
10930                    newline: true,
10931                }],
10932                ..Default::default()
10933            },
10934            autoclose_before: "}".to_string(),
10935            ..Default::default()
10936        },
10937        Some(tree_sitter_rust::LANGUAGE.into()),
10938    ));
10939
10940    let text = r#"
10941        a
10942        b
10943        c
10944    "#
10945    .unindent();
10946
10947    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10948    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10949    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10950    editor
10951        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10952        .await;
10953
10954    editor.update_in(cx, |editor, window, cx| {
10955        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10956            s.select_ranges([
10957                Point::new(0, 1)..Point::new(0, 1),
10958                Point::new(1, 1)..Point::new(1, 1),
10959                Point::new(2, 1)..Point::new(2, 1),
10960            ])
10961        });
10962
10963        editor.handle_input("{", window, cx);
10964        editor.handle_input("{", window, cx);
10965        editor.handle_input("_", window, cx);
10966        assert_eq!(
10967            editor.text(cx),
10968            "
10969                a{{_}}
10970                b{{_}}
10971                c{{_}}
10972            "
10973            .unindent()
10974        );
10975        assert_eq!(
10976            editor
10977                .selections
10978                .ranges::<Point>(&editor.display_snapshot(cx)),
10979            [
10980                Point::new(0, 4)..Point::new(0, 4),
10981                Point::new(1, 4)..Point::new(1, 4),
10982                Point::new(2, 4)..Point::new(2, 4)
10983            ]
10984        );
10985
10986        editor.backspace(&Default::default(), window, cx);
10987        editor.backspace(&Default::default(), window, cx);
10988        assert_eq!(
10989            editor.text(cx),
10990            "
10991                a{}
10992                b{}
10993                c{}
10994            "
10995            .unindent()
10996        );
10997        assert_eq!(
10998            editor
10999                .selections
11000                .ranges::<Point>(&editor.display_snapshot(cx)),
11001            [
11002                Point::new(0, 2)..Point::new(0, 2),
11003                Point::new(1, 2)..Point::new(1, 2),
11004                Point::new(2, 2)..Point::new(2, 2)
11005            ]
11006        );
11007
11008        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11009        assert_eq!(
11010            editor.text(cx),
11011            "
11012                a
11013                b
11014                c
11015            "
11016            .unindent()
11017        );
11018        assert_eq!(
11019            editor
11020                .selections
11021                .ranges::<Point>(&editor.display_snapshot(cx)),
11022            [
11023                Point::new(0, 1)..Point::new(0, 1),
11024                Point::new(1, 1)..Point::new(1, 1),
11025                Point::new(2, 1)..Point::new(2, 1)
11026            ]
11027        );
11028    });
11029}
11030
11031#[gpui::test]
11032async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11033    init_test(cx, |settings| {
11034        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11035    });
11036
11037    let mut cx = EditorTestContext::new(cx).await;
11038
11039    let language = Arc::new(Language::new(
11040        LanguageConfig {
11041            brackets: BracketPairConfig {
11042                pairs: vec![
11043                    BracketPair {
11044                        start: "{".to_string(),
11045                        end: "}".to_string(),
11046                        close: true,
11047                        surround: true,
11048                        newline: true,
11049                    },
11050                    BracketPair {
11051                        start: "(".to_string(),
11052                        end: ")".to_string(),
11053                        close: true,
11054                        surround: true,
11055                        newline: true,
11056                    },
11057                    BracketPair {
11058                        start: "[".to_string(),
11059                        end: "]".to_string(),
11060                        close: false,
11061                        surround: true,
11062                        newline: true,
11063                    },
11064                ],
11065                ..Default::default()
11066            },
11067            autoclose_before: "})]".to_string(),
11068            ..Default::default()
11069        },
11070        Some(tree_sitter_rust::LANGUAGE.into()),
11071    ));
11072
11073    cx.language_registry().add(language.clone());
11074    cx.update_buffer(|buffer, cx| {
11075        buffer.set_language(Some(language), cx);
11076    });
11077
11078    cx.set_state(
11079        &"
11080            {(ˇ)}
11081            [[ˇ]]
11082            {(ˇ)}
11083        "
11084        .unindent(),
11085    );
11086
11087    cx.update_editor(|editor, window, cx| {
11088        editor.backspace(&Default::default(), window, cx);
11089        editor.backspace(&Default::default(), window, cx);
11090    });
11091
11092    cx.assert_editor_state(
11093        &"
11094            ˇ
11095            ˇ]]
11096            ˇ
11097        "
11098        .unindent(),
11099    );
11100
11101    cx.update_editor(|editor, window, cx| {
11102        editor.handle_input("{", window, cx);
11103        editor.handle_input("{", window, cx);
11104        editor.move_right(&MoveRight, window, cx);
11105        editor.move_right(&MoveRight, window, cx);
11106        editor.move_left(&MoveLeft, window, cx);
11107        editor.move_left(&MoveLeft, window, cx);
11108        editor.backspace(&Default::default(), window, cx);
11109    });
11110
11111    cx.assert_editor_state(
11112        &"
11113            {ˇ}
11114            {ˇ}]]
11115            {ˇ}
11116        "
11117        .unindent(),
11118    );
11119
11120    cx.update_editor(|editor, window, cx| {
11121        editor.backspace(&Default::default(), window, cx);
11122    });
11123
11124    cx.assert_editor_state(
11125        &"
11126            ˇ
11127            ˇ]]
11128            ˇ
11129        "
11130        .unindent(),
11131    );
11132}
11133
11134#[gpui::test]
11135async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11136    init_test(cx, |_| {});
11137
11138    let language = Arc::new(Language::new(
11139        LanguageConfig::default(),
11140        Some(tree_sitter_rust::LANGUAGE.into()),
11141    ));
11142
11143    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11144    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11145    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11146    editor
11147        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11148        .await;
11149
11150    editor.update_in(cx, |editor, window, cx| {
11151        editor.set_auto_replace_emoji_shortcode(true);
11152
11153        editor.handle_input("Hello ", window, cx);
11154        editor.handle_input(":wave", window, cx);
11155        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11156
11157        editor.handle_input(":", window, cx);
11158        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11159
11160        editor.handle_input(" :smile", window, cx);
11161        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11162
11163        editor.handle_input(":", window, cx);
11164        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11165
11166        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11167        editor.handle_input(":wave", window, cx);
11168        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11169
11170        editor.handle_input(":", window, cx);
11171        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11172
11173        editor.handle_input(":1", window, cx);
11174        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11175
11176        editor.handle_input(":", window, cx);
11177        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11178
11179        // Ensure shortcode does not get replaced when it is part of a word
11180        editor.handle_input(" Test:wave", window, cx);
11181        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11182
11183        editor.handle_input(":", window, cx);
11184        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11185
11186        editor.set_auto_replace_emoji_shortcode(false);
11187
11188        // Ensure shortcode does not get replaced when auto replace is off
11189        editor.handle_input(" :wave", window, cx);
11190        assert_eq!(
11191            editor.text(cx),
11192            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11193        );
11194
11195        editor.handle_input(":", window, cx);
11196        assert_eq!(
11197            editor.text(cx),
11198            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11199        );
11200    });
11201}
11202
11203#[gpui::test]
11204async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11205    init_test(cx, |_| {});
11206
11207    let (text, insertion_ranges) = marked_text_ranges(
11208        indoc! {"
11209            ˇ
11210        "},
11211        false,
11212    );
11213
11214    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11215    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11216
11217    _ = editor.update_in(cx, |editor, window, cx| {
11218        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11219
11220        editor
11221            .insert_snippet(
11222                &insertion_ranges
11223                    .iter()
11224                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11225                    .collect::<Vec<_>>(),
11226                snippet,
11227                window,
11228                cx,
11229            )
11230            .unwrap();
11231
11232        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11233            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11234            assert_eq!(editor.text(cx), expected_text);
11235            assert_eq!(
11236                editor.selections.ranges(&editor.display_snapshot(cx)),
11237                selection_ranges
11238                    .iter()
11239                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11240                    .collect::<Vec<_>>()
11241            );
11242        }
11243
11244        assert(
11245            editor,
11246            cx,
11247            indoc! {"
11248            type «» =•
11249            "},
11250        );
11251
11252        assert!(editor.context_menu_visible(), "There should be a matches");
11253    });
11254}
11255
11256#[gpui::test]
11257async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11258    init_test(cx, |_| {});
11259
11260    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11261        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11262        assert_eq!(editor.text(cx), expected_text);
11263        assert_eq!(
11264            editor.selections.ranges(&editor.display_snapshot(cx)),
11265            selection_ranges
11266                .iter()
11267                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11268                .collect::<Vec<_>>()
11269        );
11270    }
11271
11272    let (text, insertion_ranges) = marked_text_ranges(
11273        indoc! {"
11274            ˇ
11275        "},
11276        false,
11277    );
11278
11279    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11280    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11281
11282    _ = editor.update_in(cx, |editor, window, cx| {
11283        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11284
11285        editor
11286            .insert_snippet(
11287                &insertion_ranges
11288                    .iter()
11289                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11290                    .collect::<Vec<_>>(),
11291                snippet,
11292                window,
11293                cx,
11294            )
11295            .unwrap();
11296
11297        assert_state(
11298            editor,
11299            cx,
11300            indoc! {"
11301            type «» = ;•
11302            "},
11303        );
11304
11305        assert!(
11306            editor.context_menu_visible(),
11307            "Context menu should be visible for placeholder choices"
11308        );
11309
11310        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11311
11312        assert_state(
11313            editor,
11314            cx,
11315            indoc! {"
11316            type  = «»;•
11317            "},
11318        );
11319
11320        assert!(
11321            !editor.context_menu_visible(),
11322            "Context menu should be hidden after moving to next tabstop"
11323        );
11324
11325        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11326
11327        assert_state(
11328            editor,
11329            cx,
11330            indoc! {"
11331            type  = ; ˇ
11332            "},
11333        );
11334
11335        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11336
11337        assert_state(
11338            editor,
11339            cx,
11340            indoc! {"
11341            type  = ; ˇ
11342            "},
11343        );
11344    });
11345
11346    _ = editor.update_in(cx, |editor, window, cx| {
11347        editor.select_all(&SelectAll, window, cx);
11348        editor.backspace(&Backspace, window, cx);
11349
11350        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11351        let insertion_ranges = editor
11352            .selections
11353            .all(&editor.display_snapshot(cx))
11354            .iter()
11355            .map(|s| s.range())
11356            .collect::<Vec<_>>();
11357
11358        editor
11359            .insert_snippet(&insertion_ranges, snippet, window, cx)
11360            .unwrap();
11361
11362        assert_state(editor, cx, "fn «» = value;•");
11363
11364        assert!(
11365            editor.context_menu_visible(),
11366            "Context menu should be visible for placeholder choices"
11367        );
11368
11369        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11370
11371        assert_state(editor, cx, "fn  = «valueˇ»;•");
11372
11373        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11374
11375        assert_state(editor, cx, "fn «» = value;•");
11376
11377        assert!(
11378            editor.context_menu_visible(),
11379            "Context menu should be visible again after returning to first tabstop"
11380        );
11381
11382        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11383
11384        assert_state(editor, cx, "fn «» = value;•");
11385    });
11386}
11387
11388#[gpui::test]
11389async fn test_snippets(cx: &mut TestAppContext) {
11390    init_test(cx, |_| {});
11391
11392    let mut cx = EditorTestContext::new(cx).await;
11393
11394    cx.set_state(indoc! {"
11395        a.ˇ b
11396        a.ˇ b
11397        a.ˇ b
11398    "});
11399
11400    cx.update_editor(|editor, window, cx| {
11401        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11402        let insertion_ranges = editor
11403            .selections
11404            .all(&editor.display_snapshot(cx))
11405            .iter()
11406            .map(|s| s.range())
11407            .collect::<Vec<_>>();
11408        editor
11409            .insert_snippet(&insertion_ranges, snippet, window, cx)
11410            .unwrap();
11411    });
11412
11413    cx.assert_editor_state(indoc! {"
11414        a.f(«oneˇ», two, «threeˇ») b
11415        a.f(«oneˇ», two, «threeˇ») b
11416        a.f(«oneˇ», two, «threeˇ») b
11417    "});
11418
11419    // Can't move earlier than the first tab stop
11420    cx.update_editor(|editor, window, cx| {
11421        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11422    });
11423    cx.assert_editor_state(indoc! {"
11424        a.f(«oneˇ», two, «threeˇ») b
11425        a.f(«oneˇ», two, «threeˇ») b
11426        a.f(«oneˇ», two, «threeˇ») b
11427    "});
11428
11429    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11430    cx.assert_editor_state(indoc! {"
11431        a.f(one, «twoˇ», three) b
11432        a.f(one, «twoˇ», three) b
11433        a.f(one, «twoˇ», three) b
11434    "});
11435
11436    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11437    cx.assert_editor_state(indoc! {"
11438        a.f(«oneˇ», two, «threeˇ») b
11439        a.f(«oneˇ», two, «threeˇ») b
11440        a.f(«oneˇ», two, «threeˇ») b
11441    "});
11442
11443    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11444    cx.assert_editor_state(indoc! {"
11445        a.f(one, «twoˇ», three) b
11446        a.f(one, «twoˇ», three) b
11447        a.f(one, «twoˇ», three) b
11448    "});
11449    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11450    cx.assert_editor_state(indoc! {"
11451        a.f(one, two, three)ˇ b
11452        a.f(one, two, three)ˇ b
11453        a.f(one, two, three)ˇ b
11454    "});
11455
11456    // As soon as the last tab stop is reached, snippet state is gone
11457    cx.update_editor(|editor, window, cx| {
11458        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11459    });
11460    cx.assert_editor_state(indoc! {"
11461        a.f(one, two, three)ˇ b
11462        a.f(one, two, three)ˇ b
11463        a.f(one, two, three)ˇ b
11464    "});
11465}
11466
11467#[gpui::test]
11468async fn test_snippet_indentation(cx: &mut TestAppContext) {
11469    init_test(cx, |_| {});
11470
11471    let mut cx = EditorTestContext::new(cx).await;
11472
11473    cx.update_editor(|editor, window, cx| {
11474        let snippet = Snippet::parse(indoc! {"
11475            /*
11476             * Multiline comment with leading indentation
11477             *
11478             * $1
11479             */
11480            $0"})
11481        .unwrap();
11482        let insertion_ranges = editor
11483            .selections
11484            .all(&editor.display_snapshot(cx))
11485            .iter()
11486            .map(|s| s.range())
11487            .collect::<Vec<_>>();
11488        editor
11489            .insert_snippet(&insertion_ranges, snippet, window, cx)
11490            .unwrap();
11491    });
11492
11493    cx.assert_editor_state(indoc! {"
11494        /*
11495         * Multiline comment with leading indentation
11496         *
11497         * ˇ
11498         */
11499    "});
11500
11501    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11502    cx.assert_editor_state(indoc! {"
11503        /*
11504         * Multiline comment with leading indentation
11505         *
11506         *•
11507         */
11508        ˇ"});
11509}
11510
11511#[gpui::test]
11512async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11513    init_test(cx, |_| {});
11514
11515    let mut cx = EditorTestContext::new(cx).await;
11516    cx.update_editor(|editor, _, cx| {
11517        editor.project().unwrap().update(cx, |project, cx| {
11518            project.snippets().update(cx, |snippets, _cx| {
11519                let snippet = project::snippet_provider::Snippet {
11520                    prefix: vec!["multi word".to_string()],
11521                    body: "this is many words".to_string(),
11522                    description: Some("description".to_string()),
11523                    name: "multi-word snippet test".to_string(),
11524                };
11525                snippets.add_snippet_for_test(
11526                    None,
11527                    PathBuf::from("test_snippets.json"),
11528                    vec![Arc::new(snippet)],
11529                );
11530            });
11531        })
11532    });
11533
11534    for (input_to_simulate, should_match_snippet) in [
11535        ("m", true),
11536        ("m ", true),
11537        ("m w", true),
11538        ("aa m w", true),
11539        ("aa m g", false),
11540    ] {
11541        cx.set_state("ˇ");
11542        cx.simulate_input(input_to_simulate); // fails correctly
11543
11544        cx.update_editor(|editor, _, _| {
11545            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11546            else {
11547                assert!(!should_match_snippet); // no completions! don't even show the menu
11548                return;
11549            };
11550            assert!(context_menu.visible());
11551            let completions = context_menu.completions.borrow();
11552
11553            assert_eq!(!completions.is_empty(), should_match_snippet);
11554        });
11555    }
11556}
11557
11558#[gpui::test]
11559async fn test_document_format_during_save(cx: &mut TestAppContext) {
11560    init_test(cx, |_| {});
11561
11562    let fs = FakeFs::new(cx.executor());
11563    fs.insert_file(path!("/file.rs"), Default::default()).await;
11564
11565    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11566
11567    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11568    language_registry.add(rust_lang());
11569    let mut fake_servers = language_registry.register_fake_lsp(
11570        "Rust",
11571        FakeLspAdapter {
11572            capabilities: lsp::ServerCapabilities {
11573                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11574                ..Default::default()
11575            },
11576            ..Default::default()
11577        },
11578    );
11579
11580    let buffer = project
11581        .update(cx, |project, cx| {
11582            project.open_local_buffer(path!("/file.rs"), cx)
11583        })
11584        .await
11585        .unwrap();
11586
11587    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11588    let (editor, cx) = cx.add_window_view(|window, cx| {
11589        build_editor_with_project(project.clone(), buffer, window, cx)
11590    });
11591    editor.update_in(cx, |editor, window, cx| {
11592        editor.set_text("one\ntwo\nthree\n", window, cx)
11593    });
11594    assert!(cx.read(|cx| editor.is_dirty(cx)));
11595
11596    cx.executor().start_waiting();
11597    let fake_server = fake_servers.next().await.unwrap();
11598
11599    {
11600        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11601            move |params, _| async move {
11602                assert_eq!(
11603                    params.text_document.uri,
11604                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11605                );
11606                assert_eq!(params.options.tab_size, 4);
11607                Ok(Some(vec![lsp::TextEdit::new(
11608                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11609                    ", ".to_string(),
11610                )]))
11611            },
11612        );
11613        let save = editor
11614            .update_in(cx, |editor, window, cx| {
11615                editor.save(
11616                    SaveOptions {
11617                        format: true,
11618                        autosave: false,
11619                    },
11620                    project.clone(),
11621                    window,
11622                    cx,
11623                )
11624            })
11625            .unwrap();
11626        cx.executor().start_waiting();
11627        save.await;
11628
11629        assert_eq!(
11630            editor.update(cx, |editor, cx| editor.text(cx)),
11631            "one, two\nthree\n"
11632        );
11633        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11634    }
11635
11636    {
11637        editor.update_in(cx, |editor, window, cx| {
11638            editor.set_text("one\ntwo\nthree\n", window, cx)
11639        });
11640        assert!(cx.read(|cx| editor.is_dirty(cx)));
11641
11642        // Ensure we can still save even if formatting hangs.
11643        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11644            move |params, _| async move {
11645                assert_eq!(
11646                    params.text_document.uri,
11647                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11648                );
11649                futures::future::pending::<()>().await;
11650                unreachable!()
11651            },
11652        );
11653        let save = editor
11654            .update_in(cx, |editor, window, cx| {
11655                editor.save(
11656                    SaveOptions {
11657                        format: true,
11658                        autosave: false,
11659                    },
11660                    project.clone(),
11661                    window,
11662                    cx,
11663                )
11664            })
11665            .unwrap();
11666        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11667        cx.executor().start_waiting();
11668        save.await;
11669        assert_eq!(
11670            editor.update(cx, |editor, cx| editor.text(cx)),
11671            "one\ntwo\nthree\n"
11672        );
11673    }
11674
11675    // Set rust language override and assert overridden tabsize is sent to language server
11676    update_test_language_settings(cx, |settings| {
11677        settings.languages.0.insert(
11678            "Rust".into(),
11679            LanguageSettingsContent {
11680                tab_size: NonZeroU32::new(8),
11681                ..Default::default()
11682            },
11683        );
11684    });
11685
11686    {
11687        editor.update_in(cx, |editor, window, cx| {
11688            editor.set_text("somehting_new\n", window, cx)
11689        });
11690        assert!(cx.read(|cx| editor.is_dirty(cx)));
11691        let _formatting_request_signal = fake_server
11692            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11693                assert_eq!(
11694                    params.text_document.uri,
11695                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11696                );
11697                assert_eq!(params.options.tab_size, 8);
11698                Ok(Some(vec![]))
11699            });
11700        let save = editor
11701            .update_in(cx, |editor, window, cx| {
11702                editor.save(
11703                    SaveOptions {
11704                        format: true,
11705                        autosave: false,
11706                    },
11707                    project.clone(),
11708                    window,
11709                    cx,
11710                )
11711            })
11712            .unwrap();
11713        cx.executor().start_waiting();
11714        save.await;
11715    }
11716}
11717
11718#[gpui::test]
11719async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11720    init_test(cx, |settings| {
11721        settings.defaults.ensure_final_newline_on_save = Some(false);
11722    });
11723
11724    let fs = FakeFs::new(cx.executor());
11725    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11726
11727    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11728
11729    let buffer = project
11730        .update(cx, |project, cx| {
11731            project.open_local_buffer(path!("/file.txt"), cx)
11732        })
11733        .await
11734        .unwrap();
11735
11736    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11737    let (editor, cx) = cx.add_window_view(|window, cx| {
11738        build_editor_with_project(project.clone(), buffer, window, cx)
11739    });
11740    editor.update_in(cx, |editor, window, cx| {
11741        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11742            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11743        });
11744    });
11745    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11746
11747    editor.update_in(cx, |editor, window, cx| {
11748        editor.handle_input("\n", window, cx)
11749    });
11750    cx.run_until_parked();
11751    save(&editor, &project, cx).await;
11752    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11753
11754    editor.update_in(cx, |editor, window, cx| {
11755        editor.undo(&Default::default(), window, cx);
11756    });
11757    save(&editor, &project, cx).await;
11758    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11759
11760    editor.update_in(cx, |editor, window, cx| {
11761        editor.redo(&Default::default(), window, cx);
11762    });
11763    cx.run_until_parked();
11764    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11765
11766    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11767        let save = editor
11768            .update_in(cx, |editor, window, cx| {
11769                editor.save(
11770                    SaveOptions {
11771                        format: true,
11772                        autosave: false,
11773                    },
11774                    project.clone(),
11775                    window,
11776                    cx,
11777                )
11778            })
11779            .unwrap();
11780        cx.executor().start_waiting();
11781        save.await;
11782        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11783    }
11784}
11785
11786#[gpui::test]
11787async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11788    init_test(cx, |_| {});
11789
11790    let cols = 4;
11791    let rows = 10;
11792    let sample_text_1 = sample_text(rows, cols, 'a');
11793    assert_eq!(
11794        sample_text_1,
11795        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11796    );
11797    let sample_text_2 = sample_text(rows, cols, 'l');
11798    assert_eq!(
11799        sample_text_2,
11800        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11801    );
11802    let sample_text_3 = sample_text(rows, cols, 'v');
11803    assert_eq!(
11804        sample_text_3,
11805        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11806    );
11807
11808    let fs = FakeFs::new(cx.executor());
11809    fs.insert_tree(
11810        path!("/a"),
11811        json!({
11812            "main.rs": sample_text_1,
11813            "other.rs": sample_text_2,
11814            "lib.rs": sample_text_3,
11815        }),
11816    )
11817    .await;
11818
11819    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11820    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11821    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11822
11823    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11824    language_registry.add(rust_lang());
11825    let mut fake_servers = language_registry.register_fake_lsp(
11826        "Rust",
11827        FakeLspAdapter {
11828            capabilities: lsp::ServerCapabilities {
11829                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11830                ..Default::default()
11831            },
11832            ..Default::default()
11833        },
11834    );
11835
11836    let worktree = project.update(cx, |project, cx| {
11837        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11838        assert_eq!(worktrees.len(), 1);
11839        worktrees.pop().unwrap()
11840    });
11841    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11842
11843    let buffer_1 = project
11844        .update(cx, |project, cx| {
11845            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11846        })
11847        .await
11848        .unwrap();
11849    let buffer_2 = project
11850        .update(cx, |project, cx| {
11851            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11852        })
11853        .await
11854        .unwrap();
11855    let buffer_3 = project
11856        .update(cx, |project, cx| {
11857            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11858        })
11859        .await
11860        .unwrap();
11861
11862    let multi_buffer = cx.new(|cx| {
11863        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11864        multi_buffer.push_excerpts(
11865            buffer_1.clone(),
11866            [
11867                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11868                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11869                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11870            ],
11871            cx,
11872        );
11873        multi_buffer.push_excerpts(
11874            buffer_2.clone(),
11875            [
11876                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11877                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11878                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11879            ],
11880            cx,
11881        );
11882        multi_buffer.push_excerpts(
11883            buffer_3.clone(),
11884            [
11885                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11886                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11887                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11888            ],
11889            cx,
11890        );
11891        multi_buffer
11892    });
11893    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11894        Editor::new(
11895            EditorMode::full(),
11896            multi_buffer,
11897            Some(project.clone()),
11898            window,
11899            cx,
11900        )
11901    });
11902
11903    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11904        editor.change_selections(
11905            SelectionEffects::scroll(Autoscroll::Next),
11906            window,
11907            cx,
11908            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
11909        );
11910        editor.insert("|one|two|three|", window, cx);
11911    });
11912    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11913    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11914        editor.change_selections(
11915            SelectionEffects::scroll(Autoscroll::Next),
11916            window,
11917            cx,
11918            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
11919        );
11920        editor.insert("|four|five|six|", window, cx);
11921    });
11922    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11923
11924    // First two buffers should be edited, but not the third one.
11925    assert_eq!(
11926        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11927        "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}",
11928    );
11929    buffer_1.update(cx, |buffer, _| {
11930        assert!(buffer.is_dirty());
11931        assert_eq!(
11932            buffer.text(),
11933            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11934        )
11935    });
11936    buffer_2.update(cx, |buffer, _| {
11937        assert!(buffer.is_dirty());
11938        assert_eq!(
11939            buffer.text(),
11940            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11941        )
11942    });
11943    buffer_3.update(cx, |buffer, _| {
11944        assert!(!buffer.is_dirty());
11945        assert_eq!(buffer.text(), sample_text_3,)
11946    });
11947    cx.executor().run_until_parked();
11948
11949    cx.executor().start_waiting();
11950    let save = multi_buffer_editor
11951        .update_in(cx, |editor, window, cx| {
11952            editor.save(
11953                SaveOptions {
11954                    format: true,
11955                    autosave: false,
11956                },
11957                project.clone(),
11958                window,
11959                cx,
11960            )
11961        })
11962        .unwrap();
11963
11964    let fake_server = fake_servers.next().await.unwrap();
11965    fake_server
11966        .server
11967        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11968            Ok(Some(vec![lsp::TextEdit::new(
11969                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11970                format!("[{} formatted]", params.text_document.uri),
11971            )]))
11972        })
11973        .detach();
11974    save.await;
11975
11976    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11977    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11978    assert_eq!(
11979        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11980        uri!(
11981            "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}"
11982        ),
11983    );
11984    buffer_1.update(cx, |buffer, _| {
11985        assert!(!buffer.is_dirty());
11986        assert_eq!(
11987            buffer.text(),
11988            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11989        )
11990    });
11991    buffer_2.update(cx, |buffer, _| {
11992        assert!(!buffer.is_dirty());
11993        assert_eq!(
11994            buffer.text(),
11995            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11996        )
11997    });
11998    buffer_3.update(cx, |buffer, _| {
11999        assert!(!buffer.is_dirty());
12000        assert_eq!(buffer.text(), sample_text_3,)
12001    });
12002}
12003
12004#[gpui::test]
12005async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12006    init_test(cx, |_| {});
12007
12008    let fs = FakeFs::new(cx.executor());
12009    fs.insert_tree(
12010        path!("/dir"),
12011        json!({
12012            "file1.rs": "fn main() { println!(\"hello\"); }",
12013            "file2.rs": "fn test() { println!(\"test\"); }",
12014            "file3.rs": "fn other() { println!(\"other\"); }\n",
12015        }),
12016    )
12017    .await;
12018
12019    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12020    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12021    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12022
12023    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12024    language_registry.add(rust_lang());
12025
12026    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12027    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12028
12029    // Open three buffers
12030    let buffer_1 = project
12031        .update(cx, |project, cx| {
12032            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12033        })
12034        .await
12035        .unwrap();
12036    let buffer_2 = project
12037        .update(cx, |project, cx| {
12038            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12039        })
12040        .await
12041        .unwrap();
12042    let buffer_3 = project
12043        .update(cx, |project, cx| {
12044            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12045        })
12046        .await
12047        .unwrap();
12048
12049    // Create a multi-buffer with all three buffers
12050    let multi_buffer = cx.new(|cx| {
12051        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12052        multi_buffer.push_excerpts(
12053            buffer_1.clone(),
12054            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12055            cx,
12056        );
12057        multi_buffer.push_excerpts(
12058            buffer_2.clone(),
12059            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12060            cx,
12061        );
12062        multi_buffer.push_excerpts(
12063            buffer_3.clone(),
12064            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12065            cx,
12066        );
12067        multi_buffer
12068    });
12069
12070    let editor = cx.new_window_entity(|window, cx| {
12071        Editor::new(
12072            EditorMode::full(),
12073            multi_buffer,
12074            Some(project.clone()),
12075            window,
12076            cx,
12077        )
12078    });
12079
12080    // Edit only the first buffer
12081    editor.update_in(cx, |editor, window, cx| {
12082        editor.change_selections(
12083            SelectionEffects::scroll(Autoscroll::Next),
12084            window,
12085            cx,
12086            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12087        );
12088        editor.insert("// edited", window, cx);
12089    });
12090
12091    // Verify that only buffer 1 is dirty
12092    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12093    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12094    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12095
12096    // Get write counts after file creation (files were created with initial content)
12097    // We expect each file to have been written once during creation
12098    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12099    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12100    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12101
12102    // Perform autosave
12103    let save_task = editor.update_in(cx, |editor, window, cx| {
12104        editor.save(
12105            SaveOptions {
12106                format: true,
12107                autosave: true,
12108            },
12109            project.clone(),
12110            window,
12111            cx,
12112        )
12113    });
12114    save_task.await.unwrap();
12115
12116    // Only the dirty buffer should have been saved
12117    assert_eq!(
12118        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12119        1,
12120        "Buffer 1 was dirty, so it should have been written once during autosave"
12121    );
12122    assert_eq!(
12123        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12124        0,
12125        "Buffer 2 was clean, so it should not have been written during autosave"
12126    );
12127    assert_eq!(
12128        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12129        0,
12130        "Buffer 3 was clean, so it should not have been written during autosave"
12131    );
12132
12133    // Verify buffer states after autosave
12134    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12135    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12136    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12137
12138    // Now perform a manual save (format = true)
12139    let save_task = editor.update_in(cx, |editor, window, cx| {
12140        editor.save(
12141            SaveOptions {
12142                format: true,
12143                autosave: false,
12144            },
12145            project.clone(),
12146            window,
12147            cx,
12148        )
12149    });
12150    save_task.await.unwrap();
12151
12152    // During manual save, clean buffers don't get written to disk
12153    // They just get did_save called for language server notifications
12154    assert_eq!(
12155        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12156        1,
12157        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12158    );
12159    assert_eq!(
12160        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12161        0,
12162        "Buffer 2 should not have been written at all"
12163    );
12164    assert_eq!(
12165        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12166        0,
12167        "Buffer 3 should not have been written at all"
12168    );
12169}
12170
12171async fn setup_range_format_test(
12172    cx: &mut TestAppContext,
12173) -> (
12174    Entity<Project>,
12175    Entity<Editor>,
12176    &mut gpui::VisualTestContext,
12177    lsp::FakeLanguageServer,
12178) {
12179    init_test(cx, |_| {});
12180
12181    let fs = FakeFs::new(cx.executor());
12182    fs.insert_file(path!("/file.rs"), Default::default()).await;
12183
12184    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12185
12186    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12187    language_registry.add(rust_lang());
12188    let mut fake_servers = language_registry.register_fake_lsp(
12189        "Rust",
12190        FakeLspAdapter {
12191            capabilities: lsp::ServerCapabilities {
12192                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12193                ..lsp::ServerCapabilities::default()
12194            },
12195            ..FakeLspAdapter::default()
12196        },
12197    );
12198
12199    let buffer = project
12200        .update(cx, |project, cx| {
12201            project.open_local_buffer(path!("/file.rs"), cx)
12202        })
12203        .await
12204        .unwrap();
12205
12206    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12207    let (editor, cx) = cx.add_window_view(|window, cx| {
12208        build_editor_with_project(project.clone(), buffer, window, cx)
12209    });
12210
12211    cx.executor().start_waiting();
12212    let fake_server = fake_servers.next().await.unwrap();
12213
12214    (project, editor, cx, fake_server)
12215}
12216
12217#[gpui::test]
12218async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12219    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12220
12221    editor.update_in(cx, |editor, window, cx| {
12222        editor.set_text("one\ntwo\nthree\n", window, cx)
12223    });
12224    assert!(cx.read(|cx| editor.is_dirty(cx)));
12225
12226    let save = editor
12227        .update_in(cx, |editor, window, cx| {
12228            editor.save(
12229                SaveOptions {
12230                    format: true,
12231                    autosave: false,
12232                },
12233                project.clone(),
12234                window,
12235                cx,
12236            )
12237        })
12238        .unwrap();
12239    fake_server
12240        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12241            assert_eq!(
12242                params.text_document.uri,
12243                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12244            );
12245            assert_eq!(params.options.tab_size, 4);
12246            Ok(Some(vec![lsp::TextEdit::new(
12247                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12248                ", ".to_string(),
12249            )]))
12250        })
12251        .next()
12252        .await;
12253    cx.executor().start_waiting();
12254    save.await;
12255    assert_eq!(
12256        editor.update(cx, |editor, cx| editor.text(cx)),
12257        "one, two\nthree\n"
12258    );
12259    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12260}
12261
12262#[gpui::test]
12263async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12264    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12265
12266    editor.update_in(cx, |editor, window, cx| {
12267        editor.set_text("one\ntwo\nthree\n", window, cx)
12268    });
12269    assert!(cx.read(|cx| editor.is_dirty(cx)));
12270
12271    // Test that save still works when formatting hangs
12272    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12273        move |params, _| async move {
12274            assert_eq!(
12275                params.text_document.uri,
12276                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12277            );
12278            futures::future::pending::<()>().await;
12279            unreachable!()
12280        },
12281    );
12282    let save = editor
12283        .update_in(cx, |editor, window, cx| {
12284            editor.save(
12285                SaveOptions {
12286                    format: true,
12287                    autosave: false,
12288                },
12289                project.clone(),
12290                window,
12291                cx,
12292            )
12293        })
12294        .unwrap();
12295    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12296    cx.executor().start_waiting();
12297    save.await;
12298    assert_eq!(
12299        editor.update(cx, |editor, cx| editor.text(cx)),
12300        "one\ntwo\nthree\n"
12301    );
12302    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12303}
12304
12305#[gpui::test]
12306async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12307    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12308
12309    // Buffer starts clean, no formatting should be requested
12310    let save = editor
12311        .update_in(cx, |editor, window, cx| {
12312            editor.save(
12313                SaveOptions {
12314                    format: false,
12315                    autosave: false,
12316                },
12317                project.clone(),
12318                window,
12319                cx,
12320            )
12321        })
12322        .unwrap();
12323    let _pending_format_request = fake_server
12324        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12325            panic!("Should not be invoked");
12326        })
12327        .next();
12328    cx.executor().start_waiting();
12329    save.await;
12330    cx.run_until_parked();
12331}
12332
12333#[gpui::test]
12334async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12335    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12336
12337    // Set Rust language override and assert overridden tabsize is sent to language server
12338    update_test_language_settings(cx, |settings| {
12339        settings.languages.0.insert(
12340            "Rust".into(),
12341            LanguageSettingsContent {
12342                tab_size: NonZeroU32::new(8),
12343                ..Default::default()
12344            },
12345        );
12346    });
12347
12348    editor.update_in(cx, |editor, window, cx| {
12349        editor.set_text("something_new\n", window, cx)
12350    });
12351    assert!(cx.read(|cx| editor.is_dirty(cx)));
12352    let save = editor
12353        .update_in(cx, |editor, window, cx| {
12354            editor.save(
12355                SaveOptions {
12356                    format: true,
12357                    autosave: false,
12358                },
12359                project.clone(),
12360                window,
12361                cx,
12362            )
12363        })
12364        .unwrap();
12365    fake_server
12366        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12367            assert_eq!(
12368                params.text_document.uri,
12369                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12370            );
12371            assert_eq!(params.options.tab_size, 8);
12372            Ok(Some(Vec::new()))
12373        })
12374        .next()
12375        .await;
12376    save.await;
12377}
12378
12379#[gpui::test]
12380async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12381    init_test(cx, |settings| {
12382        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12383            settings::LanguageServerFormatterSpecifier::Current,
12384        )))
12385    });
12386
12387    let fs = FakeFs::new(cx.executor());
12388    fs.insert_file(path!("/file.rs"), Default::default()).await;
12389
12390    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12391
12392    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12393    language_registry.add(Arc::new(Language::new(
12394        LanguageConfig {
12395            name: "Rust".into(),
12396            matcher: LanguageMatcher {
12397                path_suffixes: vec!["rs".to_string()],
12398                ..Default::default()
12399            },
12400            ..LanguageConfig::default()
12401        },
12402        Some(tree_sitter_rust::LANGUAGE.into()),
12403    )));
12404    update_test_language_settings(cx, |settings| {
12405        // Enable Prettier formatting for the same buffer, and ensure
12406        // LSP is called instead of Prettier.
12407        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12408    });
12409    let mut fake_servers = language_registry.register_fake_lsp(
12410        "Rust",
12411        FakeLspAdapter {
12412            capabilities: lsp::ServerCapabilities {
12413                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12414                ..Default::default()
12415            },
12416            ..Default::default()
12417        },
12418    );
12419
12420    let buffer = project
12421        .update(cx, |project, cx| {
12422            project.open_local_buffer(path!("/file.rs"), cx)
12423        })
12424        .await
12425        .unwrap();
12426
12427    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12428    let (editor, cx) = cx.add_window_view(|window, cx| {
12429        build_editor_with_project(project.clone(), buffer, window, cx)
12430    });
12431    editor.update_in(cx, |editor, window, cx| {
12432        editor.set_text("one\ntwo\nthree\n", window, cx)
12433    });
12434
12435    cx.executor().start_waiting();
12436    let fake_server = fake_servers.next().await.unwrap();
12437
12438    let format = editor
12439        .update_in(cx, |editor, window, cx| {
12440            editor.perform_format(
12441                project.clone(),
12442                FormatTrigger::Manual,
12443                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12444                window,
12445                cx,
12446            )
12447        })
12448        .unwrap();
12449    fake_server
12450        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12451            assert_eq!(
12452                params.text_document.uri,
12453                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12454            );
12455            assert_eq!(params.options.tab_size, 4);
12456            Ok(Some(vec![lsp::TextEdit::new(
12457                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12458                ", ".to_string(),
12459            )]))
12460        })
12461        .next()
12462        .await;
12463    cx.executor().start_waiting();
12464    format.await;
12465    assert_eq!(
12466        editor.update(cx, |editor, cx| editor.text(cx)),
12467        "one, two\nthree\n"
12468    );
12469
12470    editor.update_in(cx, |editor, window, cx| {
12471        editor.set_text("one\ntwo\nthree\n", window, cx)
12472    });
12473    // Ensure we don't lock if formatting hangs.
12474    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12475        move |params, _| async move {
12476            assert_eq!(
12477                params.text_document.uri,
12478                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12479            );
12480            futures::future::pending::<()>().await;
12481            unreachable!()
12482        },
12483    );
12484    let format = editor
12485        .update_in(cx, |editor, window, cx| {
12486            editor.perform_format(
12487                project,
12488                FormatTrigger::Manual,
12489                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12490                window,
12491                cx,
12492            )
12493        })
12494        .unwrap();
12495    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12496    cx.executor().start_waiting();
12497    format.await;
12498    assert_eq!(
12499        editor.update(cx, |editor, cx| editor.text(cx)),
12500        "one\ntwo\nthree\n"
12501    );
12502}
12503
12504#[gpui::test]
12505async fn test_multiple_formatters(cx: &mut TestAppContext) {
12506    init_test(cx, |settings| {
12507        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12508        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12509            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12510            Formatter::CodeAction("code-action-1".into()),
12511            Formatter::CodeAction("code-action-2".into()),
12512        ]))
12513    });
12514
12515    let fs = FakeFs::new(cx.executor());
12516    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12517        .await;
12518
12519    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12520    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12521    language_registry.add(rust_lang());
12522
12523    let mut fake_servers = language_registry.register_fake_lsp(
12524        "Rust",
12525        FakeLspAdapter {
12526            capabilities: lsp::ServerCapabilities {
12527                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12528                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12529                    commands: vec!["the-command-for-code-action-1".into()],
12530                    ..Default::default()
12531                }),
12532                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12533                ..Default::default()
12534            },
12535            ..Default::default()
12536        },
12537    );
12538
12539    let buffer = project
12540        .update(cx, |project, cx| {
12541            project.open_local_buffer(path!("/file.rs"), cx)
12542        })
12543        .await
12544        .unwrap();
12545
12546    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12547    let (editor, cx) = cx.add_window_view(|window, cx| {
12548        build_editor_with_project(project.clone(), buffer, window, cx)
12549    });
12550
12551    cx.executor().start_waiting();
12552
12553    let fake_server = fake_servers.next().await.unwrap();
12554    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12555        move |_params, _| async move {
12556            Ok(Some(vec![lsp::TextEdit::new(
12557                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12558                "applied-formatting\n".to_string(),
12559            )]))
12560        },
12561    );
12562    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12563        move |params, _| async move {
12564            let requested_code_actions = params.context.only.expect("Expected code action request");
12565            assert_eq!(requested_code_actions.len(), 1);
12566
12567            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12568            let code_action = match requested_code_actions[0].as_str() {
12569                "code-action-1" => lsp::CodeAction {
12570                    kind: Some("code-action-1".into()),
12571                    edit: Some(lsp::WorkspaceEdit::new(
12572                        [(
12573                            uri,
12574                            vec![lsp::TextEdit::new(
12575                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12576                                "applied-code-action-1-edit\n".to_string(),
12577                            )],
12578                        )]
12579                        .into_iter()
12580                        .collect(),
12581                    )),
12582                    command: Some(lsp::Command {
12583                        command: "the-command-for-code-action-1".into(),
12584                        ..Default::default()
12585                    }),
12586                    ..Default::default()
12587                },
12588                "code-action-2" => lsp::CodeAction {
12589                    kind: Some("code-action-2".into()),
12590                    edit: Some(lsp::WorkspaceEdit::new(
12591                        [(
12592                            uri,
12593                            vec![lsp::TextEdit::new(
12594                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12595                                "applied-code-action-2-edit\n".to_string(),
12596                            )],
12597                        )]
12598                        .into_iter()
12599                        .collect(),
12600                    )),
12601                    ..Default::default()
12602                },
12603                req => panic!("Unexpected code action request: {:?}", req),
12604            };
12605            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12606                code_action,
12607            )]))
12608        },
12609    );
12610
12611    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12612        move |params, _| async move { Ok(params) }
12613    });
12614
12615    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12616    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12617        let fake = fake_server.clone();
12618        let lock = command_lock.clone();
12619        move |params, _| {
12620            assert_eq!(params.command, "the-command-for-code-action-1");
12621            let fake = fake.clone();
12622            let lock = lock.clone();
12623            async move {
12624                lock.lock().await;
12625                fake.server
12626                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12627                        label: None,
12628                        edit: lsp::WorkspaceEdit {
12629                            changes: Some(
12630                                [(
12631                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12632                                    vec![lsp::TextEdit {
12633                                        range: lsp::Range::new(
12634                                            lsp::Position::new(0, 0),
12635                                            lsp::Position::new(0, 0),
12636                                        ),
12637                                        new_text: "applied-code-action-1-command\n".into(),
12638                                    }],
12639                                )]
12640                                .into_iter()
12641                                .collect(),
12642                            ),
12643                            ..Default::default()
12644                        },
12645                    })
12646                    .await
12647                    .into_response()
12648                    .unwrap();
12649                Ok(Some(json!(null)))
12650            }
12651        }
12652    });
12653
12654    cx.executor().start_waiting();
12655    editor
12656        .update_in(cx, |editor, window, cx| {
12657            editor.perform_format(
12658                project.clone(),
12659                FormatTrigger::Manual,
12660                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12661                window,
12662                cx,
12663            )
12664        })
12665        .unwrap()
12666        .await;
12667    editor.update(cx, |editor, cx| {
12668        assert_eq!(
12669            editor.text(cx),
12670            r#"
12671                applied-code-action-2-edit
12672                applied-code-action-1-command
12673                applied-code-action-1-edit
12674                applied-formatting
12675                one
12676                two
12677                three
12678            "#
12679            .unindent()
12680        );
12681    });
12682
12683    editor.update_in(cx, |editor, window, cx| {
12684        editor.undo(&Default::default(), window, cx);
12685        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12686    });
12687
12688    // Perform a manual edit while waiting for an LSP command
12689    // that's being run as part of a formatting code action.
12690    let lock_guard = command_lock.lock().await;
12691    let format = editor
12692        .update_in(cx, |editor, window, cx| {
12693            editor.perform_format(
12694                project.clone(),
12695                FormatTrigger::Manual,
12696                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12697                window,
12698                cx,
12699            )
12700        })
12701        .unwrap();
12702    cx.run_until_parked();
12703    editor.update(cx, |editor, cx| {
12704        assert_eq!(
12705            editor.text(cx),
12706            r#"
12707                applied-code-action-1-edit
12708                applied-formatting
12709                one
12710                two
12711                three
12712            "#
12713            .unindent()
12714        );
12715
12716        editor.buffer.update(cx, |buffer, cx| {
12717            let ix = buffer.len(cx);
12718            buffer.edit([(ix..ix, "edited\n")], None, cx);
12719        });
12720    });
12721
12722    // Allow the LSP command to proceed. Because the buffer was edited,
12723    // the second code action will not be run.
12724    drop(lock_guard);
12725    format.await;
12726    editor.update_in(cx, |editor, window, cx| {
12727        assert_eq!(
12728            editor.text(cx),
12729            r#"
12730                applied-code-action-1-command
12731                applied-code-action-1-edit
12732                applied-formatting
12733                one
12734                two
12735                three
12736                edited
12737            "#
12738            .unindent()
12739        );
12740
12741        // The manual edit is undone first, because it is the last thing the user did
12742        // (even though the command completed afterwards).
12743        editor.undo(&Default::default(), window, cx);
12744        assert_eq!(
12745            editor.text(cx),
12746            r#"
12747                applied-code-action-1-command
12748                applied-code-action-1-edit
12749                applied-formatting
12750                one
12751                two
12752                three
12753            "#
12754            .unindent()
12755        );
12756
12757        // All the formatting (including the command, which completed after the manual edit)
12758        // is undone together.
12759        editor.undo(&Default::default(), window, cx);
12760        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12761    });
12762}
12763
12764#[gpui::test]
12765async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12766    init_test(cx, |settings| {
12767        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12768            settings::LanguageServerFormatterSpecifier::Current,
12769        )]))
12770    });
12771
12772    let fs = FakeFs::new(cx.executor());
12773    fs.insert_file(path!("/file.ts"), Default::default()).await;
12774
12775    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12776
12777    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12778    language_registry.add(Arc::new(Language::new(
12779        LanguageConfig {
12780            name: "TypeScript".into(),
12781            matcher: LanguageMatcher {
12782                path_suffixes: vec!["ts".to_string()],
12783                ..Default::default()
12784            },
12785            ..LanguageConfig::default()
12786        },
12787        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12788    )));
12789    update_test_language_settings(cx, |settings| {
12790        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12791    });
12792    let mut fake_servers = language_registry.register_fake_lsp(
12793        "TypeScript",
12794        FakeLspAdapter {
12795            capabilities: lsp::ServerCapabilities {
12796                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12797                ..Default::default()
12798            },
12799            ..Default::default()
12800        },
12801    );
12802
12803    let buffer = project
12804        .update(cx, |project, cx| {
12805            project.open_local_buffer(path!("/file.ts"), cx)
12806        })
12807        .await
12808        .unwrap();
12809
12810    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12811    let (editor, cx) = cx.add_window_view(|window, cx| {
12812        build_editor_with_project(project.clone(), buffer, window, cx)
12813    });
12814    editor.update_in(cx, |editor, window, cx| {
12815        editor.set_text(
12816            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12817            window,
12818            cx,
12819        )
12820    });
12821
12822    cx.executor().start_waiting();
12823    let fake_server = fake_servers.next().await.unwrap();
12824
12825    let format = editor
12826        .update_in(cx, |editor, window, cx| {
12827            editor.perform_code_action_kind(
12828                project.clone(),
12829                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12830                window,
12831                cx,
12832            )
12833        })
12834        .unwrap();
12835    fake_server
12836        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12837            assert_eq!(
12838                params.text_document.uri,
12839                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12840            );
12841            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12842                lsp::CodeAction {
12843                    title: "Organize Imports".to_string(),
12844                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12845                    edit: Some(lsp::WorkspaceEdit {
12846                        changes: Some(
12847                            [(
12848                                params.text_document.uri.clone(),
12849                                vec![lsp::TextEdit::new(
12850                                    lsp::Range::new(
12851                                        lsp::Position::new(1, 0),
12852                                        lsp::Position::new(2, 0),
12853                                    ),
12854                                    "".to_string(),
12855                                )],
12856                            )]
12857                            .into_iter()
12858                            .collect(),
12859                        ),
12860                        ..Default::default()
12861                    }),
12862                    ..Default::default()
12863                },
12864            )]))
12865        })
12866        .next()
12867        .await;
12868    cx.executor().start_waiting();
12869    format.await;
12870    assert_eq!(
12871        editor.update(cx, |editor, cx| editor.text(cx)),
12872        "import { a } from 'module';\n\nconst x = a;\n"
12873    );
12874
12875    editor.update_in(cx, |editor, window, cx| {
12876        editor.set_text(
12877            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12878            window,
12879            cx,
12880        )
12881    });
12882    // Ensure we don't lock if code action hangs.
12883    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12884        move |params, _| async move {
12885            assert_eq!(
12886                params.text_document.uri,
12887                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12888            );
12889            futures::future::pending::<()>().await;
12890            unreachable!()
12891        },
12892    );
12893    let format = editor
12894        .update_in(cx, |editor, window, cx| {
12895            editor.perform_code_action_kind(
12896                project,
12897                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12898                window,
12899                cx,
12900            )
12901        })
12902        .unwrap();
12903    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12904    cx.executor().start_waiting();
12905    format.await;
12906    assert_eq!(
12907        editor.update(cx, |editor, cx| editor.text(cx)),
12908        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12909    );
12910}
12911
12912#[gpui::test]
12913async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12914    init_test(cx, |_| {});
12915
12916    let mut cx = EditorLspTestContext::new_rust(
12917        lsp::ServerCapabilities {
12918            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12919            ..Default::default()
12920        },
12921        cx,
12922    )
12923    .await;
12924
12925    cx.set_state(indoc! {"
12926        one.twoˇ
12927    "});
12928
12929    // The format request takes a long time. When it completes, it inserts
12930    // a newline and an indent before the `.`
12931    cx.lsp
12932        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12933            let executor = cx.background_executor().clone();
12934            async move {
12935                executor.timer(Duration::from_millis(100)).await;
12936                Ok(Some(vec![lsp::TextEdit {
12937                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12938                    new_text: "\n    ".into(),
12939                }]))
12940            }
12941        });
12942
12943    // Submit a format request.
12944    let format_1 = cx
12945        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12946        .unwrap();
12947    cx.executor().run_until_parked();
12948
12949    // Submit a second format request.
12950    let format_2 = cx
12951        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12952        .unwrap();
12953    cx.executor().run_until_parked();
12954
12955    // Wait for both format requests to complete
12956    cx.executor().advance_clock(Duration::from_millis(200));
12957    cx.executor().start_waiting();
12958    format_1.await.unwrap();
12959    cx.executor().start_waiting();
12960    format_2.await.unwrap();
12961
12962    // The formatting edits only happens once.
12963    cx.assert_editor_state(indoc! {"
12964        one
12965            .twoˇ
12966    "});
12967}
12968
12969#[gpui::test]
12970async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12971    init_test(cx, |settings| {
12972        settings.defaults.formatter = Some(FormatterList::default())
12973    });
12974
12975    let mut cx = EditorLspTestContext::new_rust(
12976        lsp::ServerCapabilities {
12977            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12978            ..Default::default()
12979        },
12980        cx,
12981    )
12982    .await;
12983
12984    // Record which buffer changes have been sent to the language server
12985    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12986    cx.lsp
12987        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12988            let buffer_changes = buffer_changes.clone();
12989            move |params, _| {
12990                buffer_changes.lock().extend(
12991                    params
12992                        .content_changes
12993                        .into_iter()
12994                        .map(|e| (e.range.unwrap(), e.text)),
12995                );
12996            }
12997        });
12998    // Handle formatting requests to the language server.
12999    cx.lsp
13000        .set_request_handler::<lsp::request::Formatting, _, _>({
13001            let buffer_changes = buffer_changes.clone();
13002            move |_, _| {
13003                let buffer_changes = buffer_changes.clone();
13004                // Insert blank lines between each line of the buffer.
13005                async move {
13006                    // When formatting is requested, trailing whitespace has already been stripped,
13007                    // and the trailing newline has already been added.
13008                    assert_eq!(
13009                        &buffer_changes.lock()[1..],
13010                        &[
13011                            (
13012                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13013                                "".into()
13014                            ),
13015                            (
13016                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13017                                "".into()
13018                            ),
13019                            (
13020                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13021                                "\n".into()
13022                            ),
13023                        ]
13024                    );
13025
13026                    Ok(Some(vec![
13027                        lsp::TextEdit {
13028                            range: lsp::Range::new(
13029                                lsp::Position::new(1, 0),
13030                                lsp::Position::new(1, 0),
13031                            ),
13032                            new_text: "\n".into(),
13033                        },
13034                        lsp::TextEdit {
13035                            range: lsp::Range::new(
13036                                lsp::Position::new(2, 0),
13037                                lsp::Position::new(2, 0),
13038                            ),
13039                            new_text: "\n".into(),
13040                        },
13041                    ]))
13042                }
13043            }
13044        });
13045
13046    // Set up a buffer white some trailing whitespace and no trailing newline.
13047    cx.set_state(
13048        &[
13049            "one ",   //
13050            "twoˇ",   //
13051            "three ", //
13052            "four",   //
13053        ]
13054        .join("\n"),
13055    );
13056    cx.run_until_parked();
13057
13058    // Submit a format request.
13059    let format = cx
13060        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13061        .unwrap();
13062
13063    cx.run_until_parked();
13064    // After formatting the buffer, the trailing whitespace is stripped,
13065    // a newline is appended, and the edits provided by the language server
13066    // have been applied.
13067    format.await.unwrap();
13068
13069    cx.assert_editor_state(
13070        &[
13071            "one",   //
13072            "",      //
13073            "twoˇ",  //
13074            "",      //
13075            "three", //
13076            "four",  //
13077            "",      //
13078        ]
13079        .join("\n"),
13080    );
13081
13082    // Undoing the formatting undoes the trailing whitespace removal, the
13083    // trailing newline, and the LSP edits.
13084    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13085    cx.assert_editor_state(
13086        &[
13087            "one ",   //
13088            "twoˇ",   //
13089            "three ", //
13090            "four",   //
13091        ]
13092        .join("\n"),
13093    );
13094}
13095
13096#[gpui::test]
13097async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13098    cx: &mut TestAppContext,
13099) {
13100    init_test(cx, |_| {});
13101
13102    cx.update(|cx| {
13103        cx.update_global::<SettingsStore, _>(|settings, cx| {
13104            settings.update_user_settings(cx, |settings| {
13105                settings.editor.auto_signature_help = Some(true);
13106            });
13107        });
13108    });
13109
13110    let mut cx = EditorLspTestContext::new_rust(
13111        lsp::ServerCapabilities {
13112            signature_help_provider: Some(lsp::SignatureHelpOptions {
13113                ..Default::default()
13114            }),
13115            ..Default::default()
13116        },
13117        cx,
13118    )
13119    .await;
13120
13121    let language = Language::new(
13122        LanguageConfig {
13123            name: "Rust".into(),
13124            brackets: BracketPairConfig {
13125                pairs: vec![
13126                    BracketPair {
13127                        start: "{".to_string(),
13128                        end: "}".to_string(),
13129                        close: true,
13130                        surround: true,
13131                        newline: true,
13132                    },
13133                    BracketPair {
13134                        start: "(".to_string(),
13135                        end: ")".to_string(),
13136                        close: true,
13137                        surround: true,
13138                        newline: true,
13139                    },
13140                    BracketPair {
13141                        start: "/*".to_string(),
13142                        end: " */".to_string(),
13143                        close: true,
13144                        surround: true,
13145                        newline: true,
13146                    },
13147                    BracketPair {
13148                        start: "[".to_string(),
13149                        end: "]".to_string(),
13150                        close: false,
13151                        surround: false,
13152                        newline: true,
13153                    },
13154                    BracketPair {
13155                        start: "\"".to_string(),
13156                        end: "\"".to_string(),
13157                        close: true,
13158                        surround: true,
13159                        newline: false,
13160                    },
13161                    BracketPair {
13162                        start: "<".to_string(),
13163                        end: ">".to_string(),
13164                        close: false,
13165                        surround: true,
13166                        newline: true,
13167                    },
13168                ],
13169                ..Default::default()
13170            },
13171            autoclose_before: "})]".to_string(),
13172            ..Default::default()
13173        },
13174        Some(tree_sitter_rust::LANGUAGE.into()),
13175    );
13176    let language = Arc::new(language);
13177
13178    cx.language_registry().add(language.clone());
13179    cx.update_buffer(|buffer, cx| {
13180        buffer.set_language(Some(language), cx);
13181    });
13182
13183    cx.set_state(
13184        &r#"
13185            fn main() {
13186                sampleˇ
13187            }
13188        "#
13189        .unindent(),
13190    );
13191
13192    cx.update_editor(|editor, window, cx| {
13193        editor.handle_input("(", window, cx);
13194    });
13195    cx.assert_editor_state(
13196        &"
13197            fn main() {
13198                sample(ˇ)
13199            }
13200        "
13201        .unindent(),
13202    );
13203
13204    let mocked_response = lsp::SignatureHelp {
13205        signatures: vec![lsp::SignatureInformation {
13206            label: "fn sample(param1: u8, param2: u8)".to_string(),
13207            documentation: None,
13208            parameters: Some(vec![
13209                lsp::ParameterInformation {
13210                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13211                    documentation: None,
13212                },
13213                lsp::ParameterInformation {
13214                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13215                    documentation: None,
13216                },
13217            ]),
13218            active_parameter: None,
13219        }],
13220        active_signature: Some(0),
13221        active_parameter: Some(0),
13222    };
13223    handle_signature_help_request(&mut cx, mocked_response).await;
13224
13225    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13226        .await;
13227
13228    cx.editor(|editor, _, _| {
13229        let signature_help_state = editor.signature_help_state.popover().cloned();
13230        let signature = signature_help_state.unwrap();
13231        assert_eq!(
13232            signature.signatures[signature.current_signature].label,
13233            "fn sample(param1: u8, param2: u8)"
13234        );
13235    });
13236}
13237
13238#[gpui::test]
13239async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13240    init_test(cx, |_| {});
13241
13242    cx.update(|cx| {
13243        cx.update_global::<SettingsStore, _>(|settings, cx| {
13244            settings.update_user_settings(cx, |settings| {
13245                settings.editor.auto_signature_help = Some(false);
13246                settings.editor.show_signature_help_after_edits = Some(false);
13247            });
13248        });
13249    });
13250
13251    let mut cx = EditorLspTestContext::new_rust(
13252        lsp::ServerCapabilities {
13253            signature_help_provider: Some(lsp::SignatureHelpOptions {
13254                ..Default::default()
13255            }),
13256            ..Default::default()
13257        },
13258        cx,
13259    )
13260    .await;
13261
13262    let language = Language::new(
13263        LanguageConfig {
13264            name: "Rust".into(),
13265            brackets: BracketPairConfig {
13266                pairs: vec![
13267                    BracketPair {
13268                        start: "{".to_string(),
13269                        end: "}".to_string(),
13270                        close: true,
13271                        surround: true,
13272                        newline: true,
13273                    },
13274                    BracketPair {
13275                        start: "(".to_string(),
13276                        end: ")".to_string(),
13277                        close: true,
13278                        surround: true,
13279                        newline: true,
13280                    },
13281                    BracketPair {
13282                        start: "/*".to_string(),
13283                        end: " */".to_string(),
13284                        close: true,
13285                        surround: true,
13286                        newline: true,
13287                    },
13288                    BracketPair {
13289                        start: "[".to_string(),
13290                        end: "]".to_string(),
13291                        close: false,
13292                        surround: false,
13293                        newline: true,
13294                    },
13295                    BracketPair {
13296                        start: "\"".to_string(),
13297                        end: "\"".to_string(),
13298                        close: true,
13299                        surround: true,
13300                        newline: false,
13301                    },
13302                    BracketPair {
13303                        start: "<".to_string(),
13304                        end: ">".to_string(),
13305                        close: false,
13306                        surround: true,
13307                        newline: true,
13308                    },
13309                ],
13310                ..Default::default()
13311            },
13312            autoclose_before: "})]".to_string(),
13313            ..Default::default()
13314        },
13315        Some(tree_sitter_rust::LANGUAGE.into()),
13316    );
13317    let language = Arc::new(language);
13318
13319    cx.language_registry().add(language.clone());
13320    cx.update_buffer(|buffer, cx| {
13321        buffer.set_language(Some(language), cx);
13322    });
13323
13324    // Ensure that signature_help is not called when no signature help is enabled.
13325    cx.set_state(
13326        &r#"
13327            fn main() {
13328                sampleˇ
13329            }
13330        "#
13331        .unindent(),
13332    );
13333    cx.update_editor(|editor, window, cx| {
13334        editor.handle_input("(", window, cx);
13335    });
13336    cx.assert_editor_state(
13337        &"
13338            fn main() {
13339                sample(ˇ)
13340            }
13341        "
13342        .unindent(),
13343    );
13344    cx.editor(|editor, _, _| {
13345        assert!(editor.signature_help_state.task().is_none());
13346    });
13347
13348    let mocked_response = lsp::SignatureHelp {
13349        signatures: vec![lsp::SignatureInformation {
13350            label: "fn sample(param1: u8, param2: u8)".to_string(),
13351            documentation: None,
13352            parameters: Some(vec![
13353                lsp::ParameterInformation {
13354                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13355                    documentation: None,
13356                },
13357                lsp::ParameterInformation {
13358                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13359                    documentation: None,
13360                },
13361            ]),
13362            active_parameter: None,
13363        }],
13364        active_signature: Some(0),
13365        active_parameter: Some(0),
13366    };
13367
13368    // Ensure that signature_help is called when enabled afte edits
13369    cx.update(|_, cx| {
13370        cx.update_global::<SettingsStore, _>(|settings, cx| {
13371            settings.update_user_settings(cx, |settings| {
13372                settings.editor.auto_signature_help = Some(false);
13373                settings.editor.show_signature_help_after_edits = Some(true);
13374            });
13375        });
13376    });
13377    cx.set_state(
13378        &r#"
13379            fn main() {
13380                sampleˇ
13381            }
13382        "#
13383        .unindent(),
13384    );
13385    cx.update_editor(|editor, window, cx| {
13386        editor.handle_input("(", window, cx);
13387    });
13388    cx.assert_editor_state(
13389        &"
13390            fn main() {
13391                sample(ˇ)
13392            }
13393        "
13394        .unindent(),
13395    );
13396    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13397    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13398        .await;
13399    cx.update_editor(|editor, _, _| {
13400        let signature_help_state = editor.signature_help_state.popover().cloned();
13401        assert!(signature_help_state.is_some());
13402        let signature = signature_help_state.unwrap();
13403        assert_eq!(
13404            signature.signatures[signature.current_signature].label,
13405            "fn sample(param1: u8, param2: u8)"
13406        );
13407        editor.signature_help_state = SignatureHelpState::default();
13408    });
13409
13410    // Ensure that signature_help is called when auto signature help override is enabled
13411    cx.update(|_, cx| {
13412        cx.update_global::<SettingsStore, _>(|settings, cx| {
13413            settings.update_user_settings(cx, |settings| {
13414                settings.editor.auto_signature_help = Some(true);
13415                settings.editor.show_signature_help_after_edits = Some(false);
13416            });
13417        });
13418    });
13419    cx.set_state(
13420        &r#"
13421            fn main() {
13422                sampleˇ
13423            }
13424        "#
13425        .unindent(),
13426    );
13427    cx.update_editor(|editor, window, cx| {
13428        editor.handle_input("(", window, cx);
13429    });
13430    cx.assert_editor_state(
13431        &"
13432            fn main() {
13433                sample(ˇ)
13434            }
13435        "
13436        .unindent(),
13437    );
13438    handle_signature_help_request(&mut cx, mocked_response).await;
13439    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13440        .await;
13441    cx.editor(|editor, _, _| {
13442        let signature_help_state = editor.signature_help_state.popover().cloned();
13443        assert!(signature_help_state.is_some());
13444        let signature = signature_help_state.unwrap();
13445        assert_eq!(
13446            signature.signatures[signature.current_signature].label,
13447            "fn sample(param1: u8, param2: u8)"
13448        );
13449    });
13450}
13451
13452#[gpui::test]
13453async fn test_signature_help(cx: &mut TestAppContext) {
13454    init_test(cx, |_| {});
13455    cx.update(|cx| {
13456        cx.update_global::<SettingsStore, _>(|settings, cx| {
13457            settings.update_user_settings(cx, |settings| {
13458                settings.editor.auto_signature_help = Some(true);
13459            });
13460        });
13461    });
13462
13463    let mut cx = EditorLspTestContext::new_rust(
13464        lsp::ServerCapabilities {
13465            signature_help_provider: Some(lsp::SignatureHelpOptions {
13466                ..Default::default()
13467            }),
13468            ..Default::default()
13469        },
13470        cx,
13471    )
13472    .await;
13473
13474    // A test that directly calls `show_signature_help`
13475    cx.update_editor(|editor, window, cx| {
13476        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13477    });
13478
13479    let mocked_response = lsp::SignatureHelp {
13480        signatures: vec![lsp::SignatureInformation {
13481            label: "fn sample(param1: u8, param2: u8)".to_string(),
13482            documentation: None,
13483            parameters: Some(vec![
13484                lsp::ParameterInformation {
13485                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13486                    documentation: None,
13487                },
13488                lsp::ParameterInformation {
13489                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13490                    documentation: None,
13491                },
13492            ]),
13493            active_parameter: None,
13494        }],
13495        active_signature: Some(0),
13496        active_parameter: Some(0),
13497    };
13498    handle_signature_help_request(&mut cx, mocked_response).await;
13499
13500    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13501        .await;
13502
13503    cx.editor(|editor, _, _| {
13504        let signature_help_state = editor.signature_help_state.popover().cloned();
13505        assert!(signature_help_state.is_some());
13506        let signature = signature_help_state.unwrap();
13507        assert_eq!(
13508            signature.signatures[signature.current_signature].label,
13509            "fn sample(param1: u8, param2: u8)"
13510        );
13511    });
13512
13513    // When exiting outside from inside the brackets, `signature_help` is closed.
13514    cx.set_state(indoc! {"
13515        fn main() {
13516            sample(ˇ);
13517        }
13518
13519        fn sample(param1: u8, param2: u8) {}
13520    "});
13521
13522    cx.update_editor(|editor, window, cx| {
13523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13524            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13525        });
13526    });
13527
13528    let mocked_response = lsp::SignatureHelp {
13529        signatures: Vec::new(),
13530        active_signature: None,
13531        active_parameter: None,
13532    };
13533    handle_signature_help_request(&mut cx, mocked_response).await;
13534
13535    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13536        .await;
13537
13538    cx.editor(|editor, _, _| {
13539        assert!(!editor.signature_help_state.is_shown());
13540    });
13541
13542    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13543    cx.set_state(indoc! {"
13544        fn main() {
13545            sample(ˇ);
13546        }
13547
13548        fn sample(param1: u8, param2: u8) {}
13549    "});
13550
13551    let mocked_response = lsp::SignatureHelp {
13552        signatures: vec![lsp::SignatureInformation {
13553            label: "fn sample(param1: u8, param2: u8)".to_string(),
13554            documentation: None,
13555            parameters: Some(vec![
13556                lsp::ParameterInformation {
13557                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13558                    documentation: None,
13559                },
13560                lsp::ParameterInformation {
13561                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13562                    documentation: None,
13563                },
13564            ]),
13565            active_parameter: None,
13566        }],
13567        active_signature: Some(0),
13568        active_parameter: Some(0),
13569    };
13570    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13571    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13572        .await;
13573    cx.editor(|editor, _, _| {
13574        assert!(editor.signature_help_state.is_shown());
13575    });
13576
13577    // Restore the popover with more parameter input
13578    cx.set_state(indoc! {"
13579        fn main() {
13580            sample(param1, param2ˇ);
13581        }
13582
13583        fn sample(param1: u8, param2: u8) {}
13584    "});
13585
13586    let mocked_response = lsp::SignatureHelp {
13587        signatures: vec![lsp::SignatureInformation {
13588            label: "fn sample(param1: u8, param2: u8)".to_string(),
13589            documentation: None,
13590            parameters: Some(vec![
13591                lsp::ParameterInformation {
13592                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13593                    documentation: None,
13594                },
13595                lsp::ParameterInformation {
13596                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13597                    documentation: None,
13598                },
13599            ]),
13600            active_parameter: None,
13601        }],
13602        active_signature: Some(0),
13603        active_parameter: Some(1),
13604    };
13605    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13606    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13607        .await;
13608
13609    // When selecting a range, the popover is gone.
13610    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13611    cx.update_editor(|editor, window, cx| {
13612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13613            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13614        })
13615    });
13616    cx.assert_editor_state(indoc! {"
13617        fn main() {
13618            sample(param1, «ˇparam2»);
13619        }
13620
13621        fn sample(param1: u8, param2: u8) {}
13622    "});
13623    cx.editor(|editor, _, _| {
13624        assert!(!editor.signature_help_state.is_shown());
13625    });
13626
13627    // When unselecting again, the popover is back if within the brackets.
13628    cx.update_editor(|editor, window, cx| {
13629        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13630            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13631        })
13632    });
13633    cx.assert_editor_state(indoc! {"
13634        fn main() {
13635            sample(param1, ˇparam2);
13636        }
13637
13638        fn sample(param1: u8, param2: u8) {}
13639    "});
13640    handle_signature_help_request(&mut cx, mocked_response).await;
13641    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13642        .await;
13643    cx.editor(|editor, _, _| {
13644        assert!(editor.signature_help_state.is_shown());
13645    });
13646
13647    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13648    cx.update_editor(|editor, window, cx| {
13649        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13650            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13651            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13652        })
13653    });
13654    cx.assert_editor_state(indoc! {"
13655        fn main() {
13656            sample(param1, ˇparam2);
13657        }
13658
13659        fn sample(param1: u8, param2: u8) {}
13660    "});
13661
13662    let mocked_response = lsp::SignatureHelp {
13663        signatures: vec![lsp::SignatureInformation {
13664            label: "fn sample(param1: u8, param2: u8)".to_string(),
13665            documentation: None,
13666            parameters: Some(vec![
13667                lsp::ParameterInformation {
13668                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13669                    documentation: None,
13670                },
13671                lsp::ParameterInformation {
13672                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13673                    documentation: None,
13674                },
13675            ]),
13676            active_parameter: None,
13677        }],
13678        active_signature: Some(0),
13679        active_parameter: Some(1),
13680    };
13681    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13682    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13683        .await;
13684    cx.update_editor(|editor, _, cx| {
13685        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13686    });
13687    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13688        .await;
13689    cx.update_editor(|editor, window, cx| {
13690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13691            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13692        })
13693    });
13694    cx.assert_editor_state(indoc! {"
13695        fn main() {
13696            sample(param1, «ˇparam2»);
13697        }
13698
13699        fn sample(param1: u8, param2: u8) {}
13700    "});
13701    cx.update_editor(|editor, window, cx| {
13702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13703            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13704        })
13705    });
13706    cx.assert_editor_state(indoc! {"
13707        fn main() {
13708            sample(param1, ˇparam2);
13709        }
13710
13711        fn sample(param1: u8, param2: u8) {}
13712    "});
13713    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13714        .await;
13715}
13716
13717#[gpui::test]
13718async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13719    init_test(cx, |_| {});
13720
13721    let mut cx = EditorLspTestContext::new_rust(
13722        lsp::ServerCapabilities {
13723            signature_help_provider: Some(lsp::SignatureHelpOptions {
13724                ..Default::default()
13725            }),
13726            ..Default::default()
13727        },
13728        cx,
13729    )
13730    .await;
13731
13732    cx.set_state(indoc! {"
13733        fn main() {
13734            overloadedˇ
13735        }
13736    "});
13737
13738    cx.update_editor(|editor, window, cx| {
13739        editor.handle_input("(", window, cx);
13740        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13741    });
13742
13743    // Mock response with 3 signatures
13744    let mocked_response = lsp::SignatureHelp {
13745        signatures: vec![
13746            lsp::SignatureInformation {
13747                label: "fn overloaded(x: i32)".to_string(),
13748                documentation: None,
13749                parameters: Some(vec![lsp::ParameterInformation {
13750                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13751                    documentation: None,
13752                }]),
13753                active_parameter: None,
13754            },
13755            lsp::SignatureInformation {
13756                label: "fn overloaded(x: i32, y: i32)".to_string(),
13757                documentation: None,
13758                parameters: Some(vec![
13759                    lsp::ParameterInformation {
13760                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13761                        documentation: None,
13762                    },
13763                    lsp::ParameterInformation {
13764                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13765                        documentation: None,
13766                    },
13767                ]),
13768                active_parameter: None,
13769            },
13770            lsp::SignatureInformation {
13771                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13772                documentation: None,
13773                parameters: Some(vec![
13774                    lsp::ParameterInformation {
13775                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13776                        documentation: None,
13777                    },
13778                    lsp::ParameterInformation {
13779                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13780                        documentation: None,
13781                    },
13782                    lsp::ParameterInformation {
13783                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13784                        documentation: None,
13785                    },
13786                ]),
13787                active_parameter: None,
13788            },
13789        ],
13790        active_signature: Some(1),
13791        active_parameter: Some(0),
13792    };
13793    handle_signature_help_request(&mut cx, mocked_response).await;
13794
13795    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13796        .await;
13797
13798    // Verify we have multiple signatures and the right one is selected
13799    cx.editor(|editor, _, _| {
13800        let popover = editor.signature_help_state.popover().cloned().unwrap();
13801        assert_eq!(popover.signatures.len(), 3);
13802        // active_signature was 1, so that should be the current
13803        assert_eq!(popover.current_signature, 1);
13804        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13805        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13806        assert_eq!(
13807            popover.signatures[2].label,
13808            "fn overloaded(x: i32, y: i32, z: i32)"
13809        );
13810    });
13811
13812    // Test navigation functionality
13813    cx.update_editor(|editor, window, cx| {
13814        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13815    });
13816
13817    cx.editor(|editor, _, _| {
13818        let popover = editor.signature_help_state.popover().cloned().unwrap();
13819        assert_eq!(popover.current_signature, 2);
13820    });
13821
13822    // Test wrap around
13823    cx.update_editor(|editor, window, cx| {
13824        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13825    });
13826
13827    cx.editor(|editor, _, _| {
13828        let popover = editor.signature_help_state.popover().cloned().unwrap();
13829        assert_eq!(popover.current_signature, 0);
13830    });
13831
13832    // Test previous navigation
13833    cx.update_editor(|editor, window, cx| {
13834        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13835    });
13836
13837    cx.editor(|editor, _, _| {
13838        let popover = editor.signature_help_state.popover().cloned().unwrap();
13839        assert_eq!(popover.current_signature, 2);
13840    });
13841}
13842
13843#[gpui::test]
13844async fn test_completion_mode(cx: &mut TestAppContext) {
13845    init_test(cx, |_| {});
13846    let mut cx = EditorLspTestContext::new_rust(
13847        lsp::ServerCapabilities {
13848            completion_provider: Some(lsp::CompletionOptions {
13849                resolve_provider: Some(true),
13850                ..Default::default()
13851            }),
13852            ..Default::default()
13853        },
13854        cx,
13855    )
13856    .await;
13857
13858    struct Run {
13859        run_description: &'static str,
13860        initial_state: String,
13861        buffer_marked_text: String,
13862        completion_label: &'static str,
13863        completion_text: &'static str,
13864        expected_with_insert_mode: String,
13865        expected_with_replace_mode: String,
13866        expected_with_replace_subsequence_mode: String,
13867        expected_with_replace_suffix_mode: String,
13868    }
13869
13870    let runs = [
13871        Run {
13872            run_description: "Start of word matches completion text",
13873            initial_state: "before ediˇ after".into(),
13874            buffer_marked_text: "before <edi|> after".into(),
13875            completion_label: "editor",
13876            completion_text: "editor",
13877            expected_with_insert_mode: "before editorˇ after".into(),
13878            expected_with_replace_mode: "before editorˇ after".into(),
13879            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13880            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13881        },
13882        Run {
13883            run_description: "Accept same text at the middle of the word",
13884            initial_state: "before ediˇtor after".into(),
13885            buffer_marked_text: "before <edi|tor> after".into(),
13886            completion_label: "editor",
13887            completion_text: "editor",
13888            expected_with_insert_mode: "before editorˇtor after".into(),
13889            expected_with_replace_mode: "before editorˇ after".into(),
13890            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13891            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13892        },
13893        Run {
13894            run_description: "End of word matches completion text -- cursor at end",
13895            initial_state: "before torˇ after".into(),
13896            buffer_marked_text: "before <tor|> after".into(),
13897            completion_label: "editor",
13898            completion_text: "editor",
13899            expected_with_insert_mode: "before editorˇ after".into(),
13900            expected_with_replace_mode: "before editorˇ after".into(),
13901            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13902            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13903        },
13904        Run {
13905            run_description: "End of word matches completion text -- cursor at start",
13906            initial_state: "before ˇtor after".into(),
13907            buffer_marked_text: "before <|tor> after".into(),
13908            completion_label: "editor",
13909            completion_text: "editor",
13910            expected_with_insert_mode: "before editorˇtor after".into(),
13911            expected_with_replace_mode: "before editorˇ after".into(),
13912            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13913            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13914        },
13915        Run {
13916            run_description: "Prepend text containing whitespace",
13917            initial_state: "pˇfield: bool".into(),
13918            buffer_marked_text: "<p|field>: bool".into(),
13919            completion_label: "pub ",
13920            completion_text: "pub ",
13921            expected_with_insert_mode: "pub ˇfield: bool".into(),
13922            expected_with_replace_mode: "pub ˇ: bool".into(),
13923            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13924            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13925        },
13926        Run {
13927            run_description: "Add element to start of list",
13928            initial_state: "[element_ˇelement_2]".into(),
13929            buffer_marked_text: "[<element_|element_2>]".into(),
13930            completion_label: "element_1",
13931            completion_text: "element_1",
13932            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13933            expected_with_replace_mode: "[element_1ˇ]".into(),
13934            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13935            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13936        },
13937        Run {
13938            run_description: "Add element to start of list -- first and second elements are equal",
13939            initial_state: "[elˇelement]".into(),
13940            buffer_marked_text: "[<el|element>]".into(),
13941            completion_label: "element",
13942            completion_text: "element",
13943            expected_with_insert_mode: "[elementˇelement]".into(),
13944            expected_with_replace_mode: "[elementˇ]".into(),
13945            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13946            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13947        },
13948        Run {
13949            run_description: "Ends with matching suffix",
13950            initial_state: "SubˇError".into(),
13951            buffer_marked_text: "<Sub|Error>".into(),
13952            completion_label: "SubscriptionError",
13953            completion_text: "SubscriptionError",
13954            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13955            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13956            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13957            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13958        },
13959        Run {
13960            run_description: "Suffix is a subsequence -- contiguous",
13961            initial_state: "SubˇErr".into(),
13962            buffer_marked_text: "<Sub|Err>".into(),
13963            completion_label: "SubscriptionError",
13964            completion_text: "SubscriptionError",
13965            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13966            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13967            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13968            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13969        },
13970        Run {
13971            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13972            initial_state: "Suˇscrirr".into(),
13973            buffer_marked_text: "<Su|scrirr>".into(),
13974            completion_label: "SubscriptionError",
13975            completion_text: "SubscriptionError",
13976            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13977            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13978            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13979            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13980        },
13981        Run {
13982            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13983            initial_state: "foo(indˇix)".into(),
13984            buffer_marked_text: "foo(<ind|ix>)".into(),
13985            completion_label: "node_index",
13986            completion_text: "node_index",
13987            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13988            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13989            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13990            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13991        },
13992        Run {
13993            run_description: "Replace range ends before cursor - should extend to cursor",
13994            initial_state: "before editˇo after".into(),
13995            buffer_marked_text: "before <{ed}>it|o after".into(),
13996            completion_label: "editor",
13997            completion_text: "editor",
13998            expected_with_insert_mode: "before editorˇo after".into(),
13999            expected_with_replace_mode: "before editorˇo after".into(),
14000            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14001            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14002        },
14003        Run {
14004            run_description: "Uses label for suffix matching",
14005            initial_state: "before ediˇtor after".into(),
14006            buffer_marked_text: "before <edi|tor> after".into(),
14007            completion_label: "editor",
14008            completion_text: "editor()",
14009            expected_with_insert_mode: "before editor()ˇtor after".into(),
14010            expected_with_replace_mode: "before editor()ˇ after".into(),
14011            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14012            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14013        },
14014        Run {
14015            run_description: "Case insensitive subsequence and suffix matching",
14016            initial_state: "before EDiˇtoR after".into(),
14017            buffer_marked_text: "before <EDi|toR> after".into(),
14018            completion_label: "editor",
14019            completion_text: "editor",
14020            expected_with_insert_mode: "before editorˇtoR after".into(),
14021            expected_with_replace_mode: "before editorˇ after".into(),
14022            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14023            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14024        },
14025    ];
14026
14027    for run in runs {
14028        let run_variations = [
14029            (LspInsertMode::Insert, run.expected_with_insert_mode),
14030            (LspInsertMode::Replace, run.expected_with_replace_mode),
14031            (
14032                LspInsertMode::ReplaceSubsequence,
14033                run.expected_with_replace_subsequence_mode,
14034            ),
14035            (
14036                LspInsertMode::ReplaceSuffix,
14037                run.expected_with_replace_suffix_mode,
14038            ),
14039        ];
14040
14041        for (lsp_insert_mode, expected_text) in run_variations {
14042            eprintln!(
14043                "run = {:?}, mode = {lsp_insert_mode:.?}",
14044                run.run_description,
14045            );
14046
14047            update_test_language_settings(&mut cx, |settings| {
14048                settings.defaults.completions = Some(CompletionSettingsContent {
14049                    lsp_insert_mode: Some(lsp_insert_mode),
14050                    words: Some(WordsCompletionMode::Disabled),
14051                    words_min_length: Some(0),
14052                    ..Default::default()
14053                });
14054            });
14055
14056            cx.set_state(&run.initial_state);
14057            cx.update_editor(|editor, window, cx| {
14058                editor.show_completions(&ShowCompletions, window, cx);
14059            });
14060
14061            let counter = Arc::new(AtomicUsize::new(0));
14062            handle_completion_request_with_insert_and_replace(
14063                &mut cx,
14064                &run.buffer_marked_text,
14065                vec![(run.completion_label, run.completion_text)],
14066                counter.clone(),
14067            )
14068            .await;
14069            cx.condition(|editor, _| editor.context_menu_visible())
14070                .await;
14071            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14072
14073            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14074                editor
14075                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14076                    .unwrap()
14077            });
14078            cx.assert_editor_state(&expected_text);
14079            handle_resolve_completion_request(&mut cx, None).await;
14080            apply_additional_edits.await.unwrap();
14081        }
14082    }
14083}
14084
14085#[gpui::test]
14086async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14087    init_test(cx, |_| {});
14088    let mut cx = EditorLspTestContext::new_rust(
14089        lsp::ServerCapabilities {
14090            completion_provider: Some(lsp::CompletionOptions {
14091                resolve_provider: Some(true),
14092                ..Default::default()
14093            }),
14094            ..Default::default()
14095        },
14096        cx,
14097    )
14098    .await;
14099
14100    let initial_state = "SubˇError";
14101    let buffer_marked_text = "<Sub|Error>";
14102    let completion_text = "SubscriptionError";
14103    let expected_with_insert_mode = "SubscriptionErrorˇError";
14104    let expected_with_replace_mode = "SubscriptionErrorˇ";
14105
14106    update_test_language_settings(&mut cx, |settings| {
14107        settings.defaults.completions = Some(CompletionSettingsContent {
14108            words: Some(WordsCompletionMode::Disabled),
14109            words_min_length: Some(0),
14110            // set the opposite here to ensure that the action is overriding the default behavior
14111            lsp_insert_mode: Some(LspInsertMode::Insert),
14112            ..Default::default()
14113        });
14114    });
14115
14116    cx.set_state(initial_state);
14117    cx.update_editor(|editor, window, cx| {
14118        editor.show_completions(&ShowCompletions, window, cx);
14119    });
14120
14121    let counter = Arc::new(AtomicUsize::new(0));
14122    handle_completion_request_with_insert_and_replace(
14123        &mut cx,
14124        buffer_marked_text,
14125        vec![(completion_text, completion_text)],
14126        counter.clone(),
14127    )
14128    .await;
14129    cx.condition(|editor, _| editor.context_menu_visible())
14130        .await;
14131    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14132
14133    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14134        editor
14135            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14136            .unwrap()
14137    });
14138    cx.assert_editor_state(expected_with_replace_mode);
14139    handle_resolve_completion_request(&mut cx, None).await;
14140    apply_additional_edits.await.unwrap();
14141
14142    update_test_language_settings(&mut cx, |settings| {
14143        settings.defaults.completions = Some(CompletionSettingsContent {
14144            words: Some(WordsCompletionMode::Disabled),
14145            words_min_length: Some(0),
14146            // set the opposite here to ensure that the action is overriding the default behavior
14147            lsp_insert_mode: Some(LspInsertMode::Replace),
14148            ..Default::default()
14149        });
14150    });
14151
14152    cx.set_state(initial_state);
14153    cx.update_editor(|editor, window, cx| {
14154        editor.show_completions(&ShowCompletions, window, cx);
14155    });
14156    handle_completion_request_with_insert_and_replace(
14157        &mut cx,
14158        buffer_marked_text,
14159        vec![(completion_text, completion_text)],
14160        counter.clone(),
14161    )
14162    .await;
14163    cx.condition(|editor, _| editor.context_menu_visible())
14164        .await;
14165    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14166
14167    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14168        editor
14169            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14170            .unwrap()
14171    });
14172    cx.assert_editor_state(expected_with_insert_mode);
14173    handle_resolve_completion_request(&mut cx, None).await;
14174    apply_additional_edits.await.unwrap();
14175}
14176
14177#[gpui::test]
14178async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14179    init_test(cx, |_| {});
14180    let mut cx = EditorLspTestContext::new_rust(
14181        lsp::ServerCapabilities {
14182            completion_provider: Some(lsp::CompletionOptions {
14183                resolve_provider: Some(true),
14184                ..Default::default()
14185            }),
14186            ..Default::default()
14187        },
14188        cx,
14189    )
14190    .await;
14191
14192    // scenario: surrounding text matches completion text
14193    let completion_text = "to_offset";
14194    let initial_state = indoc! {"
14195        1. buf.to_offˇsuffix
14196        2. buf.to_offˇsuf
14197        3. buf.to_offˇfix
14198        4. buf.to_offˇ
14199        5. into_offˇensive
14200        6. ˇsuffix
14201        7. let ˇ //
14202        8. aaˇzz
14203        9. buf.to_off«zzzzzˇ»suffix
14204        10. buf.«ˇzzzzz»suffix
14205        11. to_off«ˇzzzzz»
14206
14207        buf.to_offˇsuffix  // newest cursor
14208    "};
14209    let completion_marked_buffer = indoc! {"
14210        1. buf.to_offsuffix
14211        2. buf.to_offsuf
14212        3. buf.to_offfix
14213        4. buf.to_off
14214        5. into_offensive
14215        6. suffix
14216        7. let  //
14217        8. aazz
14218        9. buf.to_offzzzzzsuffix
14219        10. buf.zzzzzsuffix
14220        11. to_offzzzzz
14221
14222        buf.<to_off|suffix>  // newest cursor
14223    "};
14224    let expected = indoc! {"
14225        1. buf.to_offsetˇ
14226        2. buf.to_offsetˇsuf
14227        3. buf.to_offsetˇfix
14228        4. buf.to_offsetˇ
14229        5. into_offsetˇensive
14230        6. to_offsetˇsuffix
14231        7. let to_offsetˇ //
14232        8. aato_offsetˇzz
14233        9. buf.to_offsetˇ
14234        10. buf.to_offsetˇsuffix
14235        11. to_offsetˇ
14236
14237        buf.to_offsetˇ  // newest cursor
14238    "};
14239    cx.set_state(initial_state);
14240    cx.update_editor(|editor, window, cx| {
14241        editor.show_completions(&ShowCompletions, window, cx);
14242    });
14243    handle_completion_request_with_insert_and_replace(
14244        &mut cx,
14245        completion_marked_buffer,
14246        vec![(completion_text, completion_text)],
14247        Arc::new(AtomicUsize::new(0)),
14248    )
14249    .await;
14250    cx.condition(|editor, _| editor.context_menu_visible())
14251        .await;
14252    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14253        editor
14254            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14255            .unwrap()
14256    });
14257    cx.assert_editor_state(expected);
14258    handle_resolve_completion_request(&mut cx, None).await;
14259    apply_additional_edits.await.unwrap();
14260
14261    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14262    let completion_text = "foo_and_bar";
14263    let initial_state = indoc! {"
14264        1. ooanbˇ
14265        2. zooanbˇ
14266        3. ooanbˇz
14267        4. zooanbˇz
14268        5. ooanˇ
14269        6. oanbˇ
14270
14271        ooanbˇ
14272    "};
14273    let completion_marked_buffer = indoc! {"
14274        1. ooanb
14275        2. zooanb
14276        3. ooanbz
14277        4. zooanbz
14278        5. ooan
14279        6. oanb
14280
14281        <ooanb|>
14282    "};
14283    let expected = indoc! {"
14284        1. foo_and_barˇ
14285        2. zfoo_and_barˇ
14286        3. foo_and_barˇz
14287        4. zfoo_and_barˇz
14288        5. ooanfoo_and_barˇ
14289        6. oanbfoo_and_barˇ
14290
14291        foo_and_barˇ
14292    "};
14293    cx.set_state(initial_state);
14294    cx.update_editor(|editor, window, cx| {
14295        editor.show_completions(&ShowCompletions, window, cx);
14296    });
14297    handle_completion_request_with_insert_and_replace(
14298        &mut cx,
14299        completion_marked_buffer,
14300        vec![(completion_text, completion_text)],
14301        Arc::new(AtomicUsize::new(0)),
14302    )
14303    .await;
14304    cx.condition(|editor, _| editor.context_menu_visible())
14305        .await;
14306    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14307        editor
14308            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14309            .unwrap()
14310    });
14311    cx.assert_editor_state(expected);
14312    handle_resolve_completion_request(&mut cx, None).await;
14313    apply_additional_edits.await.unwrap();
14314
14315    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14316    // (expects the same as if it was inserted at the end)
14317    let completion_text = "foo_and_bar";
14318    let initial_state = indoc! {"
14319        1. ooˇanb
14320        2. zooˇanb
14321        3. ooˇanbz
14322        4. zooˇanbz
14323
14324        ooˇanb
14325    "};
14326    let completion_marked_buffer = indoc! {"
14327        1. ooanb
14328        2. zooanb
14329        3. ooanbz
14330        4. zooanbz
14331
14332        <oo|anb>
14333    "};
14334    let expected = indoc! {"
14335        1. foo_and_barˇ
14336        2. zfoo_and_barˇ
14337        3. foo_and_barˇz
14338        4. zfoo_and_barˇz
14339
14340        foo_and_barˇ
14341    "};
14342    cx.set_state(initial_state);
14343    cx.update_editor(|editor, window, cx| {
14344        editor.show_completions(&ShowCompletions, window, cx);
14345    });
14346    handle_completion_request_with_insert_and_replace(
14347        &mut cx,
14348        completion_marked_buffer,
14349        vec![(completion_text, completion_text)],
14350        Arc::new(AtomicUsize::new(0)),
14351    )
14352    .await;
14353    cx.condition(|editor, _| editor.context_menu_visible())
14354        .await;
14355    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14356        editor
14357            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14358            .unwrap()
14359    });
14360    cx.assert_editor_state(expected);
14361    handle_resolve_completion_request(&mut cx, None).await;
14362    apply_additional_edits.await.unwrap();
14363}
14364
14365// This used to crash
14366#[gpui::test]
14367async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14368    init_test(cx, |_| {});
14369
14370    let buffer_text = indoc! {"
14371        fn main() {
14372            10.satu;
14373
14374            //
14375            // separate cursors so they open in different excerpts (manually reproducible)
14376            //
14377
14378            10.satu20;
14379        }
14380    "};
14381    let multibuffer_text_with_selections = indoc! {"
14382        fn main() {
14383            10.satuˇ;
14384
14385            //
14386
14387            //
14388
14389            10.satuˇ20;
14390        }
14391    "};
14392    let expected_multibuffer = indoc! {"
14393        fn main() {
14394            10.saturating_sub()ˇ;
14395
14396            //
14397
14398            //
14399
14400            10.saturating_sub()ˇ;
14401        }
14402    "};
14403
14404    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14405    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14406
14407    let fs = FakeFs::new(cx.executor());
14408    fs.insert_tree(
14409        path!("/a"),
14410        json!({
14411            "main.rs": buffer_text,
14412        }),
14413    )
14414    .await;
14415
14416    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14417    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14418    language_registry.add(rust_lang());
14419    let mut fake_servers = language_registry.register_fake_lsp(
14420        "Rust",
14421        FakeLspAdapter {
14422            capabilities: lsp::ServerCapabilities {
14423                completion_provider: Some(lsp::CompletionOptions {
14424                    resolve_provider: None,
14425                    ..lsp::CompletionOptions::default()
14426                }),
14427                ..lsp::ServerCapabilities::default()
14428            },
14429            ..FakeLspAdapter::default()
14430        },
14431    );
14432    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14433    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14434    let buffer = project
14435        .update(cx, |project, cx| {
14436            project.open_local_buffer(path!("/a/main.rs"), cx)
14437        })
14438        .await
14439        .unwrap();
14440
14441    let multi_buffer = cx.new(|cx| {
14442        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14443        multi_buffer.push_excerpts(
14444            buffer.clone(),
14445            [ExcerptRange::new(0..first_excerpt_end)],
14446            cx,
14447        );
14448        multi_buffer.push_excerpts(
14449            buffer.clone(),
14450            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14451            cx,
14452        );
14453        multi_buffer
14454    });
14455
14456    let editor = workspace
14457        .update(cx, |_, window, cx| {
14458            cx.new(|cx| {
14459                Editor::new(
14460                    EditorMode::Full {
14461                        scale_ui_elements_with_buffer_font_size: false,
14462                        show_active_line_background: false,
14463                        sizing_behavior: SizingBehavior::Default,
14464                    },
14465                    multi_buffer.clone(),
14466                    Some(project.clone()),
14467                    window,
14468                    cx,
14469                )
14470            })
14471        })
14472        .unwrap();
14473
14474    let pane = workspace
14475        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14476        .unwrap();
14477    pane.update_in(cx, |pane, window, cx| {
14478        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14479    });
14480
14481    let fake_server = fake_servers.next().await.unwrap();
14482
14483    editor.update_in(cx, |editor, window, cx| {
14484        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14485            s.select_ranges([
14486                Point::new(1, 11)..Point::new(1, 11),
14487                Point::new(7, 11)..Point::new(7, 11),
14488            ])
14489        });
14490
14491        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14492    });
14493
14494    editor.update_in(cx, |editor, window, cx| {
14495        editor.show_completions(&ShowCompletions, window, cx);
14496    });
14497
14498    fake_server
14499        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14500            let completion_item = lsp::CompletionItem {
14501                label: "saturating_sub()".into(),
14502                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14503                    lsp::InsertReplaceEdit {
14504                        new_text: "saturating_sub()".to_owned(),
14505                        insert: lsp::Range::new(
14506                            lsp::Position::new(7, 7),
14507                            lsp::Position::new(7, 11),
14508                        ),
14509                        replace: lsp::Range::new(
14510                            lsp::Position::new(7, 7),
14511                            lsp::Position::new(7, 13),
14512                        ),
14513                    },
14514                )),
14515                ..lsp::CompletionItem::default()
14516            };
14517
14518            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14519        })
14520        .next()
14521        .await
14522        .unwrap();
14523
14524    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14525        .await;
14526
14527    editor
14528        .update_in(cx, |editor, window, cx| {
14529            editor
14530                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14531                .unwrap()
14532        })
14533        .await
14534        .unwrap();
14535
14536    editor.update(cx, |editor, cx| {
14537        assert_text_with_selections(editor, expected_multibuffer, cx);
14538    })
14539}
14540
14541#[gpui::test]
14542async fn test_completion(cx: &mut TestAppContext) {
14543    init_test(cx, |_| {});
14544
14545    let mut cx = EditorLspTestContext::new_rust(
14546        lsp::ServerCapabilities {
14547            completion_provider: Some(lsp::CompletionOptions {
14548                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14549                resolve_provider: Some(true),
14550                ..Default::default()
14551            }),
14552            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14553            ..Default::default()
14554        },
14555        cx,
14556    )
14557    .await;
14558    let counter = Arc::new(AtomicUsize::new(0));
14559
14560    cx.set_state(indoc! {"
14561        oneˇ
14562        two
14563        three
14564    "});
14565    cx.simulate_keystroke(".");
14566    handle_completion_request(
14567        indoc! {"
14568            one.|<>
14569            two
14570            three
14571        "},
14572        vec!["first_completion", "second_completion"],
14573        true,
14574        counter.clone(),
14575        &mut cx,
14576    )
14577    .await;
14578    cx.condition(|editor, _| editor.context_menu_visible())
14579        .await;
14580    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14581
14582    let _handler = handle_signature_help_request(
14583        &mut cx,
14584        lsp::SignatureHelp {
14585            signatures: vec![lsp::SignatureInformation {
14586                label: "test signature".to_string(),
14587                documentation: None,
14588                parameters: Some(vec![lsp::ParameterInformation {
14589                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14590                    documentation: None,
14591                }]),
14592                active_parameter: None,
14593            }],
14594            active_signature: None,
14595            active_parameter: None,
14596        },
14597    );
14598    cx.update_editor(|editor, window, cx| {
14599        assert!(
14600            !editor.signature_help_state.is_shown(),
14601            "No signature help was called for"
14602        );
14603        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14604    });
14605    cx.run_until_parked();
14606    cx.update_editor(|editor, _, _| {
14607        assert!(
14608            !editor.signature_help_state.is_shown(),
14609            "No signature help should be shown when completions menu is open"
14610        );
14611    });
14612
14613    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14614        editor.context_menu_next(&Default::default(), window, cx);
14615        editor
14616            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14617            .unwrap()
14618    });
14619    cx.assert_editor_state(indoc! {"
14620        one.second_completionˇ
14621        two
14622        three
14623    "});
14624
14625    handle_resolve_completion_request(
14626        &mut cx,
14627        Some(vec![
14628            (
14629                //This overlaps with the primary completion edit which is
14630                //misbehavior from the LSP spec, test that we filter it out
14631                indoc! {"
14632                    one.second_ˇcompletion
14633                    two
14634                    threeˇ
14635                "},
14636                "overlapping additional edit",
14637            ),
14638            (
14639                indoc! {"
14640                    one.second_completion
14641                    two
14642                    threeˇ
14643                "},
14644                "\nadditional edit",
14645            ),
14646        ]),
14647    )
14648    .await;
14649    apply_additional_edits.await.unwrap();
14650    cx.assert_editor_state(indoc! {"
14651        one.second_completionˇ
14652        two
14653        three
14654        additional edit
14655    "});
14656
14657    cx.set_state(indoc! {"
14658        one.second_completion
14659        twoˇ
14660        threeˇ
14661        additional edit
14662    "});
14663    cx.simulate_keystroke(" ");
14664    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14665    cx.simulate_keystroke("s");
14666    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14667
14668    cx.assert_editor_state(indoc! {"
14669        one.second_completion
14670        two sˇ
14671        three sˇ
14672        additional edit
14673    "});
14674    handle_completion_request(
14675        indoc! {"
14676            one.second_completion
14677            two s
14678            three <s|>
14679            additional edit
14680        "},
14681        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14682        true,
14683        counter.clone(),
14684        &mut cx,
14685    )
14686    .await;
14687    cx.condition(|editor, _| editor.context_menu_visible())
14688        .await;
14689    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14690
14691    cx.simulate_keystroke("i");
14692
14693    handle_completion_request(
14694        indoc! {"
14695            one.second_completion
14696            two si
14697            three <si|>
14698            additional edit
14699        "},
14700        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14701        true,
14702        counter.clone(),
14703        &mut cx,
14704    )
14705    .await;
14706    cx.condition(|editor, _| editor.context_menu_visible())
14707        .await;
14708    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14709
14710    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14711        editor
14712            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14713            .unwrap()
14714    });
14715    cx.assert_editor_state(indoc! {"
14716        one.second_completion
14717        two sixth_completionˇ
14718        three sixth_completionˇ
14719        additional edit
14720    "});
14721
14722    apply_additional_edits.await.unwrap();
14723
14724    update_test_language_settings(&mut cx, |settings| {
14725        settings.defaults.show_completions_on_input = Some(false);
14726    });
14727    cx.set_state("editorˇ");
14728    cx.simulate_keystroke(".");
14729    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14730    cx.simulate_keystrokes("c l o");
14731    cx.assert_editor_state("editor.cloˇ");
14732    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14733    cx.update_editor(|editor, window, cx| {
14734        editor.show_completions(&ShowCompletions, window, cx);
14735    });
14736    handle_completion_request(
14737        "editor.<clo|>",
14738        vec!["close", "clobber"],
14739        true,
14740        counter.clone(),
14741        &mut cx,
14742    )
14743    .await;
14744    cx.condition(|editor, _| editor.context_menu_visible())
14745        .await;
14746    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14747
14748    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14749        editor
14750            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14751            .unwrap()
14752    });
14753    cx.assert_editor_state("editor.clobberˇ");
14754    handle_resolve_completion_request(&mut cx, None).await;
14755    apply_additional_edits.await.unwrap();
14756}
14757
14758#[gpui::test]
14759async fn test_completion_reuse(cx: &mut TestAppContext) {
14760    init_test(cx, |_| {});
14761
14762    let mut cx = EditorLspTestContext::new_rust(
14763        lsp::ServerCapabilities {
14764            completion_provider: Some(lsp::CompletionOptions {
14765                trigger_characters: Some(vec![".".to_string()]),
14766                ..Default::default()
14767            }),
14768            ..Default::default()
14769        },
14770        cx,
14771    )
14772    .await;
14773
14774    let counter = Arc::new(AtomicUsize::new(0));
14775    cx.set_state("objˇ");
14776    cx.simulate_keystroke(".");
14777
14778    // Initial completion request returns complete results
14779    let is_incomplete = false;
14780    handle_completion_request(
14781        "obj.|<>",
14782        vec!["a", "ab", "abc"],
14783        is_incomplete,
14784        counter.clone(),
14785        &mut cx,
14786    )
14787    .await;
14788    cx.run_until_parked();
14789    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14790    cx.assert_editor_state("obj.ˇ");
14791    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14792
14793    // Type "a" - filters existing completions
14794    cx.simulate_keystroke("a");
14795    cx.run_until_parked();
14796    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14797    cx.assert_editor_state("obj.aˇ");
14798    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14799
14800    // Type "b" - filters existing completions
14801    cx.simulate_keystroke("b");
14802    cx.run_until_parked();
14803    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14804    cx.assert_editor_state("obj.abˇ");
14805    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14806
14807    // Type "c" - filters existing completions
14808    cx.simulate_keystroke("c");
14809    cx.run_until_parked();
14810    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14811    cx.assert_editor_state("obj.abcˇ");
14812    check_displayed_completions(vec!["abc"], &mut cx);
14813
14814    // Backspace to delete "c" - filters existing completions
14815    cx.update_editor(|editor, window, cx| {
14816        editor.backspace(&Backspace, window, cx);
14817    });
14818    cx.run_until_parked();
14819    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14820    cx.assert_editor_state("obj.abˇ");
14821    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14822
14823    // Moving cursor to the left dismisses menu.
14824    cx.update_editor(|editor, window, cx| {
14825        editor.move_left(&MoveLeft, window, cx);
14826    });
14827    cx.run_until_parked();
14828    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14829    cx.assert_editor_state("obj.aˇb");
14830    cx.update_editor(|editor, _, _| {
14831        assert_eq!(editor.context_menu_visible(), false);
14832    });
14833
14834    // Type "b" - new request
14835    cx.simulate_keystroke("b");
14836    let is_incomplete = false;
14837    handle_completion_request(
14838        "obj.<ab|>a",
14839        vec!["ab", "abc"],
14840        is_incomplete,
14841        counter.clone(),
14842        &mut cx,
14843    )
14844    .await;
14845    cx.run_until_parked();
14846    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14847    cx.assert_editor_state("obj.abˇb");
14848    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14849
14850    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14851    cx.update_editor(|editor, window, cx| {
14852        editor.backspace(&Backspace, window, cx);
14853    });
14854    let is_incomplete = false;
14855    handle_completion_request(
14856        "obj.<a|>b",
14857        vec!["a", "ab", "abc"],
14858        is_incomplete,
14859        counter.clone(),
14860        &mut cx,
14861    )
14862    .await;
14863    cx.run_until_parked();
14864    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14865    cx.assert_editor_state("obj.aˇb");
14866    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14867
14868    // Backspace to delete "a" - dismisses menu.
14869    cx.update_editor(|editor, window, cx| {
14870        editor.backspace(&Backspace, window, cx);
14871    });
14872    cx.run_until_parked();
14873    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14874    cx.assert_editor_state("obj.ˇb");
14875    cx.update_editor(|editor, _, _| {
14876        assert_eq!(editor.context_menu_visible(), false);
14877    });
14878}
14879
14880#[gpui::test]
14881async fn test_word_completion(cx: &mut TestAppContext) {
14882    let lsp_fetch_timeout_ms = 10;
14883    init_test(cx, |language_settings| {
14884        language_settings.defaults.completions = Some(CompletionSettingsContent {
14885            words_min_length: Some(0),
14886            lsp_fetch_timeout_ms: Some(10),
14887            lsp_insert_mode: Some(LspInsertMode::Insert),
14888            ..Default::default()
14889        });
14890    });
14891
14892    let mut cx = EditorLspTestContext::new_rust(
14893        lsp::ServerCapabilities {
14894            completion_provider: Some(lsp::CompletionOptions {
14895                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14896                ..lsp::CompletionOptions::default()
14897            }),
14898            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14899            ..lsp::ServerCapabilities::default()
14900        },
14901        cx,
14902    )
14903    .await;
14904
14905    let throttle_completions = Arc::new(AtomicBool::new(false));
14906
14907    let lsp_throttle_completions = throttle_completions.clone();
14908    let _completion_requests_handler =
14909        cx.lsp
14910            .server
14911            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14912                let lsp_throttle_completions = lsp_throttle_completions.clone();
14913                let cx = cx.clone();
14914                async move {
14915                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14916                        cx.background_executor()
14917                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14918                            .await;
14919                    }
14920                    Ok(Some(lsp::CompletionResponse::Array(vec![
14921                        lsp::CompletionItem {
14922                            label: "first".into(),
14923                            ..lsp::CompletionItem::default()
14924                        },
14925                        lsp::CompletionItem {
14926                            label: "last".into(),
14927                            ..lsp::CompletionItem::default()
14928                        },
14929                    ])))
14930                }
14931            });
14932
14933    cx.set_state(indoc! {"
14934        oneˇ
14935        two
14936        three
14937    "});
14938    cx.simulate_keystroke(".");
14939    cx.executor().run_until_parked();
14940    cx.condition(|editor, _| editor.context_menu_visible())
14941        .await;
14942    cx.update_editor(|editor, window, cx| {
14943        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14944        {
14945            assert_eq!(
14946                completion_menu_entries(menu),
14947                &["first", "last"],
14948                "When LSP server is fast to reply, no fallback word completions are used"
14949            );
14950        } else {
14951            panic!("expected completion menu to be open");
14952        }
14953        editor.cancel(&Cancel, window, cx);
14954    });
14955    cx.executor().run_until_parked();
14956    cx.condition(|editor, _| !editor.context_menu_visible())
14957        .await;
14958
14959    throttle_completions.store(true, atomic::Ordering::Release);
14960    cx.simulate_keystroke(".");
14961    cx.executor()
14962        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14963    cx.executor().run_until_parked();
14964    cx.condition(|editor, _| editor.context_menu_visible())
14965        .await;
14966    cx.update_editor(|editor, _, _| {
14967        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14968        {
14969            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14970                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14971        } else {
14972            panic!("expected completion menu to be open");
14973        }
14974    });
14975}
14976
14977#[gpui::test]
14978async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14979    init_test(cx, |language_settings| {
14980        language_settings.defaults.completions = Some(CompletionSettingsContent {
14981            words: Some(WordsCompletionMode::Enabled),
14982            words_min_length: Some(0),
14983            lsp_insert_mode: Some(LspInsertMode::Insert),
14984            ..Default::default()
14985        });
14986    });
14987
14988    let mut cx = EditorLspTestContext::new_rust(
14989        lsp::ServerCapabilities {
14990            completion_provider: Some(lsp::CompletionOptions {
14991                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14992                ..lsp::CompletionOptions::default()
14993            }),
14994            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14995            ..lsp::ServerCapabilities::default()
14996        },
14997        cx,
14998    )
14999    .await;
15000
15001    let _completion_requests_handler =
15002        cx.lsp
15003            .server
15004            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15005                Ok(Some(lsp::CompletionResponse::Array(vec![
15006                    lsp::CompletionItem {
15007                        label: "first".into(),
15008                        ..lsp::CompletionItem::default()
15009                    },
15010                    lsp::CompletionItem {
15011                        label: "last".into(),
15012                        ..lsp::CompletionItem::default()
15013                    },
15014                ])))
15015            });
15016
15017    cx.set_state(indoc! {"ˇ
15018        first
15019        last
15020        second
15021    "});
15022    cx.simulate_keystroke(".");
15023    cx.executor().run_until_parked();
15024    cx.condition(|editor, _| editor.context_menu_visible())
15025        .await;
15026    cx.update_editor(|editor, _, _| {
15027        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15028        {
15029            assert_eq!(
15030                completion_menu_entries(menu),
15031                &["first", "last", "second"],
15032                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15033            );
15034        } else {
15035            panic!("expected completion menu to be open");
15036        }
15037    });
15038}
15039
15040#[gpui::test]
15041async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15042    init_test(cx, |language_settings| {
15043        language_settings.defaults.completions = Some(CompletionSettingsContent {
15044            words: Some(WordsCompletionMode::Disabled),
15045            words_min_length: Some(0),
15046            lsp_insert_mode: Some(LspInsertMode::Insert),
15047            ..Default::default()
15048        });
15049    });
15050
15051    let mut cx = EditorLspTestContext::new_rust(
15052        lsp::ServerCapabilities {
15053            completion_provider: Some(lsp::CompletionOptions {
15054                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15055                ..lsp::CompletionOptions::default()
15056            }),
15057            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15058            ..lsp::ServerCapabilities::default()
15059        },
15060        cx,
15061    )
15062    .await;
15063
15064    let _completion_requests_handler =
15065        cx.lsp
15066            .server
15067            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15068                panic!("LSP completions should not be queried when dealing with word completions")
15069            });
15070
15071    cx.set_state(indoc! {"ˇ
15072        first
15073        last
15074        second
15075    "});
15076    cx.update_editor(|editor, window, cx| {
15077        editor.show_word_completions(&ShowWordCompletions, window, cx);
15078    });
15079    cx.executor().run_until_parked();
15080    cx.condition(|editor, _| editor.context_menu_visible())
15081        .await;
15082    cx.update_editor(|editor, _, _| {
15083        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15084        {
15085            assert_eq!(
15086                completion_menu_entries(menu),
15087                &["first", "last", "second"],
15088                "`ShowWordCompletions` action should show word completions"
15089            );
15090        } else {
15091            panic!("expected completion menu to be open");
15092        }
15093    });
15094
15095    cx.simulate_keystroke("l");
15096    cx.executor().run_until_parked();
15097    cx.condition(|editor, _| editor.context_menu_visible())
15098        .await;
15099    cx.update_editor(|editor, _, _| {
15100        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15101        {
15102            assert_eq!(
15103                completion_menu_entries(menu),
15104                &["last"],
15105                "After showing word completions, further editing should filter them and not query the LSP"
15106            );
15107        } else {
15108            panic!("expected completion menu to be open");
15109        }
15110    });
15111}
15112
15113#[gpui::test]
15114async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15115    init_test(cx, |language_settings| {
15116        language_settings.defaults.completions = Some(CompletionSettingsContent {
15117            words_min_length: Some(0),
15118            lsp: Some(false),
15119            lsp_insert_mode: Some(LspInsertMode::Insert),
15120            ..Default::default()
15121        });
15122    });
15123
15124    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15125
15126    cx.set_state(indoc! {"ˇ
15127        0_usize
15128        let
15129        33
15130        4.5f32
15131    "});
15132    cx.update_editor(|editor, window, cx| {
15133        editor.show_completions(&ShowCompletions, window, cx);
15134    });
15135    cx.executor().run_until_parked();
15136    cx.condition(|editor, _| editor.context_menu_visible())
15137        .await;
15138    cx.update_editor(|editor, window, cx| {
15139        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15140        {
15141            assert_eq!(
15142                completion_menu_entries(menu),
15143                &["let"],
15144                "With no digits in the completion query, no digits should be in the word completions"
15145            );
15146        } else {
15147            panic!("expected completion menu to be open");
15148        }
15149        editor.cancel(&Cancel, window, cx);
15150    });
15151
15152    cx.set_state(indoc! {"15153        0_usize
15154        let
15155        3
15156        33.35f32
15157    "});
15158    cx.update_editor(|editor, window, cx| {
15159        editor.show_completions(&ShowCompletions, window, cx);
15160    });
15161    cx.executor().run_until_parked();
15162    cx.condition(|editor, _| editor.context_menu_visible())
15163        .await;
15164    cx.update_editor(|editor, _, _| {
15165        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15166        {
15167            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15168                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15169        } else {
15170            panic!("expected completion menu to be open");
15171        }
15172    });
15173}
15174
15175#[gpui::test]
15176async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15177    init_test(cx, |language_settings| {
15178        language_settings.defaults.completions = Some(CompletionSettingsContent {
15179            words: Some(WordsCompletionMode::Enabled),
15180            words_min_length: Some(3),
15181            lsp_insert_mode: Some(LspInsertMode::Insert),
15182            ..Default::default()
15183        });
15184    });
15185
15186    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15187    cx.set_state(indoc! {"ˇ
15188        wow
15189        wowen
15190        wowser
15191    "});
15192    cx.simulate_keystroke("w");
15193    cx.executor().run_until_parked();
15194    cx.update_editor(|editor, _, _| {
15195        if editor.context_menu.borrow_mut().is_some() {
15196            panic!(
15197                "expected completion menu to be hidden, as words completion threshold is not met"
15198            );
15199        }
15200    });
15201
15202    cx.update_editor(|editor, window, cx| {
15203        editor.show_word_completions(&ShowWordCompletions, window, cx);
15204    });
15205    cx.executor().run_until_parked();
15206    cx.update_editor(|editor, window, cx| {
15207        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15208        {
15209            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
15210        } else {
15211            panic!("expected completion menu to be open after the word completions are called with an action");
15212        }
15213
15214        editor.cancel(&Cancel, window, cx);
15215    });
15216    cx.update_editor(|editor, _, _| {
15217        if editor.context_menu.borrow_mut().is_some() {
15218            panic!("expected completion menu to be hidden after canceling");
15219        }
15220    });
15221
15222    cx.simulate_keystroke("o");
15223    cx.executor().run_until_parked();
15224    cx.update_editor(|editor, _, _| {
15225        if editor.context_menu.borrow_mut().is_some() {
15226            panic!(
15227                "expected completion menu to be hidden, as words completion threshold is not met still"
15228            );
15229        }
15230    });
15231
15232    cx.simulate_keystroke("w");
15233    cx.executor().run_until_parked();
15234    cx.update_editor(|editor, _, _| {
15235        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15236        {
15237            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15238        } else {
15239            panic!("expected completion menu to be open after the word completions threshold is met");
15240        }
15241    });
15242}
15243
15244#[gpui::test]
15245async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15246    init_test(cx, |language_settings| {
15247        language_settings.defaults.completions = Some(CompletionSettingsContent {
15248            words: Some(WordsCompletionMode::Enabled),
15249            words_min_length: Some(0),
15250            lsp_insert_mode: Some(LspInsertMode::Insert),
15251            ..Default::default()
15252        });
15253    });
15254
15255    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15256    cx.update_editor(|editor, _, _| {
15257        editor.disable_word_completions();
15258    });
15259    cx.set_state(indoc! {"ˇ
15260        wow
15261        wowen
15262        wowser
15263    "});
15264    cx.simulate_keystroke("w");
15265    cx.executor().run_until_parked();
15266    cx.update_editor(|editor, _, _| {
15267        if editor.context_menu.borrow_mut().is_some() {
15268            panic!(
15269                "expected completion menu to be hidden, as words completion are disabled for this editor"
15270            );
15271        }
15272    });
15273
15274    cx.update_editor(|editor, window, cx| {
15275        editor.show_word_completions(&ShowWordCompletions, window, cx);
15276    });
15277    cx.executor().run_until_parked();
15278    cx.update_editor(|editor, _, _| {
15279        if editor.context_menu.borrow_mut().is_some() {
15280            panic!(
15281                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15282            );
15283        }
15284    });
15285}
15286
15287#[gpui::test]
15288async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15289    init_test(cx, |language_settings| {
15290        language_settings.defaults.completions = Some(CompletionSettingsContent {
15291            words: Some(WordsCompletionMode::Disabled),
15292            words_min_length: Some(0),
15293            lsp_insert_mode: Some(LspInsertMode::Insert),
15294            ..Default::default()
15295        });
15296    });
15297
15298    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15299    cx.update_editor(|editor, _, _| {
15300        editor.set_completion_provider(None);
15301    });
15302    cx.set_state(indoc! {"ˇ
15303        wow
15304        wowen
15305        wowser
15306    "});
15307    cx.simulate_keystroke("w");
15308    cx.executor().run_until_parked();
15309    cx.update_editor(|editor, _, _| {
15310        if editor.context_menu.borrow_mut().is_some() {
15311            panic!("expected completion menu to be hidden, as disabled in settings");
15312        }
15313    });
15314}
15315
15316fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15317    let position = || lsp::Position {
15318        line: params.text_document_position.position.line,
15319        character: params.text_document_position.position.character,
15320    };
15321    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15322        range: lsp::Range {
15323            start: position(),
15324            end: position(),
15325        },
15326        new_text: text.to_string(),
15327    }))
15328}
15329
15330#[gpui::test]
15331async fn test_multiline_completion(cx: &mut TestAppContext) {
15332    init_test(cx, |_| {});
15333
15334    let fs = FakeFs::new(cx.executor());
15335    fs.insert_tree(
15336        path!("/a"),
15337        json!({
15338            "main.ts": "a",
15339        }),
15340    )
15341    .await;
15342
15343    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15344    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15345    let typescript_language = Arc::new(Language::new(
15346        LanguageConfig {
15347            name: "TypeScript".into(),
15348            matcher: LanguageMatcher {
15349                path_suffixes: vec!["ts".to_string()],
15350                ..LanguageMatcher::default()
15351            },
15352            line_comments: vec!["// ".into()],
15353            ..LanguageConfig::default()
15354        },
15355        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15356    ));
15357    language_registry.add(typescript_language.clone());
15358    let mut fake_servers = language_registry.register_fake_lsp(
15359        "TypeScript",
15360        FakeLspAdapter {
15361            capabilities: lsp::ServerCapabilities {
15362                completion_provider: Some(lsp::CompletionOptions {
15363                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15364                    ..lsp::CompletionOptions::default()
15365                }),
15366                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15367                ..lsp::ServerCapabilities::default()
15368            },
15369            // Emulate vtsls label generation
15370            label_for_completion: Some(Box::new(|item, _| {
15371                let text = if let Some(description) = item
15372                    .label_details
15373                    .as_ref()
15374                    .and_then(|label_details| label_details.description.as_ref())
15375                {
15376                    format!("{} {}", item.label, description)
15377                } else if let Some(detail) = &item.detail {
15378                    format!("{} {}", item.label, detail)
15379                } else {
15380                    item.label.clone()
15381                };
15382                Some(language::CodeLabel::plain(text, None))
15383            })),
15384            ..FakeLspAdapter::default()
15385        },
15386    );
15387    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15388    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15389    let worktree_id = workspace
15390        .update(cx, |workspace, _window, cx| {
15391            workspace.project().update(cx, |project, cx| {
15392                project.worktrees(cx).next().unwrap().read(cx).id()
15393            })
15394        })
15395        .unwrap();
15396    let _buffer = project
15397        .update(cx, |project, cx| {
15398            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15399        })
15400        .await
15401        .unwrap();
15402    let editor = workspace
15403        .update(cx, |workspace, window, cx| {
15404            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15405        })
15406        .unwrap()
15407        .await
15408        .unwrap()
15409        .downcast::<Editor>()
15410        .unwrap();
15411    let fake_server = fake_servers.next().await.unwrap();
15412
15413    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15414    let multiline_label_2 = "a\nb\nc\n";
15415    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15416    let multiline_description = "d\ne\nf\n";
15417    let multiline_detail_2 = "g\nh\ni\n";
15418
15419    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15420        move |params, _| async move {
15421            Ok(Some(lsp::CompletionResponse::Array(vec![
15422                lsp::CompletionItem {
15423                    label: multiline_label.to_string(),
15424                    text_edit: gen_text_edit(&params, "new_text_1"),
15425                    ..lsp::CompletionItem::default()
15426                },
15427                lsp::CompletionItem {
15428                    label: "single line label 1".to_string(),
15429                    detail: Some(multiline_detail.to_string()),
15430                    text_edit: gen_text_edit(&params, "new_text_2"),
15431                    ..lsp::CompletionItem::default()
15432                },
15433                lsp::CompletionItem {
15434                    label: "single line label 2".to_string(),
15435                    label_details: Some(lsp::CompletionItemLabelDetails {
15436                        description: Some(multiline_description.to_string()),
15437                        detail: None,
15438                    }),
15439                    text_edit: gen_text_edit(&params, "new_text_2"),
15440                    ..lsp::CompletionItem::default()
15441                },
15442                lsp::CompletionItem {
15443                    label: multiline_label_2.to_string(),
15444                    detail: Some(multiline_detail_2.to_string()),
15445                    text_edit: gen_text_edit(&params, "new_text_3"),
15446                    ..lsp::CompletionItem::default()
15447                },
15448                lsp::CompletionItem {
15449                    label: "Label with many     spaces and \t but without newlines".to_string(),
15450                    detail: Some(
15451                        "Details with many     spaces and \t but without newlines".to_string(),
15452                    ),
15453                    text_edit: gen_text_edit(&params, "new_text_4"),
15454                    ..lsp::CompletionItem::default()
15455                },
15456            ])))
15457        },
15458    );
15459
15460    editor.update_in(cx, |editor, window, cx| {
15461        cx.focus_self(window);
15462        editor.move_to_end(&MoveToEnd, window, cx);
15463        editor.handle_input(".", window, cx);
15464    });
15465    cx.run_until_parked();
15466    completion_handle.next().await.unwrap();
15467
15468    editor.update(cx, |editor, _| {
15469        assert!(editor.context_menu_visible());
15470        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15471        {
15472            let completion_labels = menu
15473                .completions
15474                .borrow()
15475                .iter()
15476                .map(|c| c.label.text.clone())
15477                .collect::<Vec<_>>();
15478            assert_eq!(
15479                completion_labels,
15480                &[
15481                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15482                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15483                    "single line label 2 d e f ",
15484                    "a b c g h i ",
15485                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15486                ],
15487                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15488            );
15489
15490            for completion in menu
15491                .completions
15492                .borrow()
15493                .iter() {
15494                    assert_eq!(
15495                        completion.label.filter_range,
15496                        0..completion.label.text.len(),
15497                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15498                    );
15499                }
15500        } else {
15501            panic!("expected completion menu to be open");
15502        }
15503    });
15504}
15505
15506#[gpui::test]
15507async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15508    init_test(cx, |_| {});
15509    let mut cx = EditorLspTestContext::new_rust(
15510        lsp::ServerCapabilities {
15511            completion_provider: Some(lsp::CompletionOptions {
15512                trigger_characters: Some(vec![".".to_string()]),
15513                ..Default::default()
15514            }),
15515            ..Default::default()
15516        },
15517        cx,
15518    )
15519    .await;
15520    cx.lsp
15521        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15522            Ok(Some(lsp::CompletionResponse::Array(vec![
15523                lsp::CompletionItem {
15524                    label: "first".into(),
15525                    ..Default::default()
15526                },
15527                lsp::CompletionItem {
15528                    label: "last".into(),
15529                    ..Default::default()
15530                },
15531            ])))
15532        });
15533    cx.set_state("variableˇ");
15534    cx.simulate_keystroke(".");
15535    cx.executor().run_until_parked();
15536
15537    cx.update_editor(|editor, _, _| {
15538        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15539        {
15540            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15541        } else {
15542            panic!("expected completion menu to be open");
15543        }
15544    });
15545
15546    cx.update_editor(|editor, window, cx| {
15547        editor.move_page_down(&MovePageDown::default(), window, cx);
15548        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15549        {
15550            assert!(
15551                menu.selected_item == 1,
15552                "expected PageDown to select the last item from the context menu"
15553            );
15554        } else {
15555            panic!("expected completion menu to stay open after PageDown");
15556        }
15557    });
15558
15559    cx.update_editor(|editor, window, cx| {
15560        editor.move_page_up(&MovePageUp::default(), window, cx);
15561        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15562        {
15563            assert!(
15564                menu.selected_item == 0,
15565                "expected PageUp to select the first item from the context menu"
15566            );
15567        } else {
15568            panic!("expected completion menu to stay open after PageUp");
15569        }
15570    });
15571}
15572
15573#[gpui::test]
15574async fn test_as_is_completions(cx: &mut TestAppContext) {
15575    init_test(cx, |_| {});
15576    let mut cx = EditorLspTestContext::new_rust(
15577        lsp::ServerCapabilities {
15578            completion_provider: Some(lsp::CompletionOptions {
15579                ..Default::default()
15580            }),
15581            ..Default::default()
15582        },
15583        cx,
15584    )
15585    .await;
15586    cx.lsp
15587        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15588            Ok(Some(lsp::CompletionResponse::Array(vec![
15589                lsp::CompletionItem {
15590                    label: "unsafe".into(),
15591                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15592                        range: lsp::Range {
15593                            start: lsp::Position {
15594                                line: 1,
15595                                character: 2,
15596                            },
15597                            end: lsp::Position {
15598                                line: 1,
15599                                character: 3,
15600                            },
15601                        },
15602                        new_text: "unsafe".to_string(),
15603                    })),
15604                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15605                    ..Default::default()
15606                },
15607            ])))
15608        });
15609    cx.set_state("fn a() {}\n");
15610    cx.executor().run_until_parked();
15611    cx.update_editor(|editor, window, cx| {
15612        editor.trigger_completion_on_input("n", true, window, cx)
15613    });
15614    cx.executor().run_until_parked();
15615
15616    cx.update_editor(|editor, window, cx| {
15617        editor.confirm_completion(&Default::default(), window, cx)
15618    });
15619    cx.executor().run_until_parked();
15620    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15621}
15622
15623#[gpui::test]
15624async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15625    init_test(cx, |_| {});
15626    let language =
15627        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15628    let mut cx = EditorLspTestContext::new(
15629        language,
15630        lsp::ServerCapabilities {
15631            completion_provider: Some(lsp::CompletionOptions {
15632                ..lsp::CompletionOptions::default()
15633            }),
15634            ..lsp::ServerCapabilities::default()
15635        },
15636        cx,
15637    )
15638    .await;
15639
15640    cx.set_state(
15641        "#ifndef BAR_H
15642#define BAR_H
15643
15644#include <stdbool.h>
15645
15646int fn_branch(bool do_branch1, bool do_branch2);
15647
15648#endif // BAR_H
15649ˇ",
15650    );
15651    cx.executor().run_until_parked();
15652    cx.update_editor(|editor, window, cx| {
15653        editor.handle_input("#", window, cx);
15654    });
15655    cx.executor().run_until_parked();
15656    cx.update_editor(|editor, window, cx| {
15657        editor.handle_input("i", window, cx);
15658    });
15659    cx.executor().run_until_parked();
15660    cx.update_editor(|editor, window, cx| {
15661        editor.handle_input("n", window, cx);
15662    });
15663    cx.executor().run_until_parked();
15664    cx.assert_editor_state(
15665        "#ifndef BAR_H
15666#define BAR_H
15667
15668#include <stdbool.h>
15669
15670int fn_branch(bool do_branch1, bool do_branch2);
15671
15672#endif // BAR_H
15673#inˇ",
15674    );
15675
15676    cx.lsp
15677        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15678            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15679                is_incomplete: false,
15680                item_defaults: None,
15681                items: vec![lsp::CompletionItem {
15682                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15683                    label_details: Some(lsp::CompletionItemLabelDetails {
15684                        detail: Some("header".to_string()),
15685                        description: None,
15686                    }),
15687                    label: " include".to_string(),
15688                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15689                        range: lsp::Range {
15690                            start: lsp::Position {
15691                                line: 8,
15692                                character: 1,
15693                            },
15694                            end: lsp::Position {
15695                                line: 8,
15696                                character: 1,
15697                            },
15698                        },
15699                        new_text: "include \"$0\"".to_string(),
15700                    })),
15701                    sort_text: Some("40b67681include".to_string()),
15702                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15703                    filter_text: Some("include".to_string()),
15704                    insert_text: Some("include \"$0\"".to_string()),
15705                    ..lsp::CompletionItem::default()
15706                }],
15707            })))
15708        });
15709    cx.update_editor(|editor, window, cx| {
15710        editor.show_completions(&ShowCompletions, window, cx);
15711    });
15712    cx.executor().run_until_parked();
15713    cx.update_editor(|editor, window, cx| {
15714        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15715    });
15716    cx.executor().run_until_parked();
15717    cx.assert_editor_state(
15718        "#ifndef BAR_H
15719#define BAR_H
15720
15721#include <stdbool.h>
15722
15723int fn_branch(bool do_branch1, bool do_branch2);
15724
15725#endif // BAR_H
15726#include \"ˇ\"",
15727    );
15728
15729    cx.lsp
15730        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15731            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15732                is_incomplete: true,
15733                item_defaults: None,
15734                items: vec![lsp::CompletionItem {
15735                    kind: Some(lsp::CompletionItemKind::FILE),
15736                    label: "AGL/".to_string(),
15737                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15738                        range: lsp::Range {
15739                            start: lsp::Position {
15740                                line: 8,
15741                                character: 10,
15742                            },
15743                            end: lsp::Position {
15744                                line: 8,
15745                                character: 11,
15746                            },
15747                        },
15748                        new_text: "AGL/".to_string(),
15749                    })),
15750                    sort_text: Some("40b67681AGL/".to_string()),
15751                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15752                    filter_text: Some("AGL/".to_string()),
15753                    insert_text: Some("AGL/".to_string()),
15754                    ..lsp::CompletionItem::default()
15755                }],
15756            })))
15757        });
15758    cx.update_editor(|editor, window, cx| {
15759        editor.show_completions(&ShowCompletions, window, cx);
15760    });
15761    cx.executor().run_until_parked();
15762    cx.update_editor(|editor, window, cx| {
15763        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15764    });
15765    cx.executor().run_until_parked();
15766    cx.assert_editor_state(
15767        r##"#ifndef BAR_H
15768#define BAR_H
15769
15770#include <stdbool.h>
15771
15772int fn_branch(bool do_branch1, bool do_branch2);
15773
15774#endif // BAR_H
15775#include "AGL/ˇ"##,
15776    );
15777
15778    cx.update_editor(|editor, window, cx| {
15779        editor.handle_input("\"", window, cx);
15780    });
15781    cx.executor().run_until_parked();
15782    cx.assert_editor_state(
15783        r##"#ifndef BAR_H
15784#define BAR_H
15785
15786#include <stdbool.h>
15787
15788int fn_branch(bool do_branch1, bool do_branch2);
15789
15790#endif // BAR_H
15791#include "AGL/"ˇ"##,
15792    );
15793}
15794
15795#[gpui::test]
15796async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15797    init_test(cx, |_| {});
15798
15799    let mut cx = EditorLspTestContext::new_rust(
15800        lsp::ServerCapabilities {
15801            completion_provider: Some(lsp::CompletionOptions {
15802                trigger_characters: Some(vec![".".to_string()]),
15803                resolve_provider: Some(true),
15804                ..Default::default()
15805            }),
15806            ..Default::default()
15807        },
15808        cx,
15809    )
15810    .await;
15811
15812    cx.set_state("fn main() { let a = 2ˇ; }");
15813    cx.simulate_keystroke(".");
15814    let completion_item = lsp::CompletionItem {
15815        label: "Some".into(),
15816        kind: Some(lsp::CompletionItemKind::SNIPPET),
15817        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15818        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15819            kind: lsp::MarkupKind::Markdown,
15820            value: "```rust\nSome(2)\n```".to_string(),
15821        })),
15822        deprecated: Some(false),
15823        sort_text: Some("Some".to_string()),
15824        filter_text: Some("Some".to_string()),
15825        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15826        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15827            range: lsp::Range {
15828                start: lsp::Position {
15829                    line: 0,
15830                    character: 22,
15831                },
15832                end: lsp::Position {
15833                    line: 0,
15834                    character: 22,
15835                },
15836            },
15837            new_text: "Some(2)".to_string(),
15838        })),
15839        additional_text_edits: Some(vec![lsp::TextEdit {
15840            range: lsp::Range {
15841                start: lsp::Position {
15842                    line: 0,
15843                    character: 20,
15844                },
15845                end: lsp::Position {
15846                    line: 0,
15847                    character: 22,
15848                },
15849            },
15850            new_text: "".to_string(),
15851        }]),
15852        ..Default::default()
15853    };
15854
15855    let closure_completion_item = completion_item.clone();
15856    let counter = Arc::new(AtomicUsize::new(0));
15857    let counter_clone = counter.clone();
15858    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15859        let task_completion_item = closure_completion_item.clone();
15860        counter_clone.fetch_add(1, atomic::Ordering::Release);
15861        async move {
15862            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15863                is_incomplete: true,
15864                item_defaults: None,
15865                items: vec![task_completion_item],
15866            })))
15867        }
15868    });
15869
15870    cx.condition(|editor, _| editor.context_menu_visible())
15871        .await;
15872    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15873    assert!(request.next().await.is_some());
15874    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15875
15876    cx.simulate_keystrokes("S o m");
15877    cx.condition(|editor, _| editor.context_menu_visible())
15878        .await;
15879    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15880    assert!(request.next().await.is_some());
15881    assert!(request.next().await.is_some());
15882    assert!(request.next().await.is_some());
15883    request.close();
15884    assert!(request.next().await.is_none());
15885    assert_eq!(
15886        counter.load(atomic::Ordering::Acquire),
15887        4,
15888        "With the completions menu open, only one LSP request should happen per input"
15889    );
15890}
15891
15892#[gpui::test]
15893async fn test_toggle_comment(cx: &mut TestAppContext) {
15894    init_test(cx, |_| {});
15895    let mut cx = EditorTestContext::new(cx).await;
15896    let language = Arc::new(Language::new(
15897        LanguageConfig {
15898            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15899            ..Default::default()
15900        },
15901        Some(tree_sitter_rust::LANGUAGE.into()),
15902    ));
15903    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
15904
15905    // If multiple selections intersect a line, the line is only toggled once.
15906    cx.set_state(indoc! {"
15907        fn a() {
15908            «//b();
15909            ˇ»// «c();
15910            //ˇ»  d();
15911        }
15912    "});
15913
15914    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15915
15916    cx.assert_editor_state(indoc! {"
15917        fn a() {
15918            «b();
15919            c();
15920            ˇ» d();
15921        }
15922    "});
15923
15924    // The comment prefix is inserted at the same column for every line in a
15925    // selection.
15926    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15927
15928    cx.assert_editor_state(indoc! {"
15929        fn a() {
15930            // «b();
15931            // c();
15932            ˇ»//  d();
15933        }
15934    "});
15935
15936    // If a selection ends at the beginning of a line, that line is not toggled.
15937    cx.set_selections_state(indoc! {"
15938        fn a() {
15939            // b();
15940            «// c();
15941        ˇ»    //  d();
15942        }
15943    "});
15944
15945    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15946
15947    cx.assert_editor_state(indoc! {"
15948        fn a() {
15949            // b();
15950            «c();
15951        ˇ»    //  d();
15952        }
15953    "});
15954
15955    // If a selection span a single line and is empty, the line is toggled.
15956    cx.set_state(indoc! {"
15957        fn a() {
15958            a();
15959            b();
15960        ˇ
15961        }
15962    "});
15963
15964    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15965
15966    cx.assert_editor_state(indoc! {"
15967        fn a() {
15968            a();
15969            b();
15970        //•ˇ
15971        }
15972    "});
15973
15974    // If a selection span multiple lines, empty lines are not toggled.
15975    cx.set_state(indoc! {"
15976        fn a() {
15977            «a();
15978
15979            c();ˇ»
15980        }
15981    "});
15982
15983    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15984
15985    cx.assert_editor_state(indoc! {"
15986        fn a() {
15987            // «a();
15988
15989            // c();ˇ»
15990        }
15991    "});
15992
15993    // If a selection includes multiple comment prefixes, all lines are uncommented.
15994    cx.set_state(indoc! {"
15995        fn a() {
15996            «// a();
15997            /// b();
15998            //! c();ˇ»
15999        }
16000    "});
16001
16002    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16003
16004    cx.assert_editor_state(indoc! {"
16005        fn a() {
16006            «a();
16007            b();
16008            c();ˇ»
16009        }
16010    "});
16011}
16012
16013#[gpui::test]
16014async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16015    init_test(cx, |_| {});
16016    let mut cx = EditorTestContext::new(cx).await;
16017    let language = Arc::new(Language::new(
16018        LanguageConfig {
16019            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16020            ..Default::default()
16021        },
16022        Some(tree_sitter_rust::LANGUAGE.into()),
16023    ));
16024    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16025
16026    let toggle_comments = &ToggleComments {
16027        advance_downwards: false,
16028        ignore_indent: true,
16029    };
16030
16031    // If multiple selections intersect a line, the line is only toggled once.
16032    cx.set_state(indoc! {"
16033        fn a() {
16034        //    «b();
16035        //    c();
16036        //    ˇ» d();
16037        }
16038    "});
16039
16040    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16041
16042    cx.assert_editor_state(indoc! {"
16043        fn a() {
16044            «b();
16045            c();
16046            ˇ» d();
16047        }
16048    "});
16049
16050    // The comment prefix is inserted at the beginning of each line
16051    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16052
16053    cx.assert_editor_state(indoc! {"
16054        fn a() {
16055        //    «b();
16056        //    c();
16057        //    ˇ» d();
16058        }
16059    "});
16060
16061    // If a selection ends at the beginning of a line, that line is not toggled.
16062    cx.set_selections_state(indoc! {"
16063        fn a() {
16064        //    b();
16065        //    «c();
16066        ˇ»//     d();
16067        }
16068    "});
16069
16070    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16071
16072    cx.assert_editor_state(indoc! {"
16073        fn a() {
16074        //    b();
16075            «c();
16076        ˇ»//     d();
16077        }
16078    "});
16079
16080    // If a selection span a single line and is empty, the line is toggled.
16081    cx.set_state(indoc! {"
16082        fn a() {
16083            a();
16084            b();
16085        ˇ
16086        }
16087    "});
16088
16089    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16090
16091    cx.assert_editor_state(indoc! {"
16092        fn a() {
16093            a();
16094            b();
16095        //ˇ
16096        }
16097    "});
16098
16099    // If a selection span multiple lines, empty lines are not toggled.
16100    cx.set_state(indoc! {"
16101        fn a() {
16102            «a();
16103
16104            c();ˇ»
16105        }
16106    "});
16107
16108    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16109
16110    cx.assert_editor_state(indoc! {"
16111        fn a() {
16112        //    «a();
16113
16114        //    c();ˇ»
16115        }
16116    "});
16117
16118    // If a selection includes multiple comment prefixes, all lines are uncommented.
16119    cx.set_state(indoc! {"
16120        fn a() {
16121        //    «a();
16122        ///    b();
16123        //!    c();ˇ»
16124        }
16125    "});
16126
16127    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16128
16129    cx.assert_editor_state(indoc! {"
16130        fn a() {
16131            «a();
16132            b();
16133            c();ˇ»
16134        }
16135    "});
16136}
16137
16138#[gpui::test]
16139async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16140    init_test(cx, |_| {});
16141
16142    let language = Arc::new(Language::new(
16143        LanguageConfig {
16144            line_comments: vec!["// ".into()],
16145            ..Default::default()
16146        },
16147        Some(tree_sitter_rust::LANGUAGE.into()),
16148    ));
16149
16150    let mut cx = EditorTestContext::new(cx).await;
16151
16152    cx.language_registry().add(language.clone());
16153    cx.update_buffer(|buffer, cx| {
16154        buffer.set_language(Some(language), cx);
16155    });
16156
16157    let toggle_comments = &ToggleComments {
16158        advance_downwards: true,
16159        ignore_indent: false,
16160    };
16161
16162    // Single cursor on one line -> advance
16163    // Cursor moves horizontally 3 characters as well on non-blank line
16164    cx.set_state(indoc!(
16165        "fn a() {
16166             ˇdog();
16167             cat();
16168        }"
16169    ));
16170    cx.update_editor(|editor, window, cx| {
16171        editor.toggle_comments(toggle_comments, window, cx);
16172    });
16173    cx.assert_editor_state(indoc!(
16174        "fn a() {
16175             // dog();
16176             catˇ();
16177        }"
16178    ));
16179
16180    // Single selection on one line -> don't advance
16181    cx.set_state(indoc!(
16182        "fn a() {
16183             «dog()ˇ»;
16184             cat();
16185        }"
16186    ));
16187    cx.update_editor(|editor, window, cx| {
16188        editor.toggle_comments(toggle_comments, window, cx);
16189    });
16190    cx.assert_editor_state(indoc!(
16191        "fn a() {
16192             // «dog()ˇ»;
16193             cat();
16194        }"
16195    ));
16196
16197    // Multiple cursors on one line -> advance
16198    cx.set_state(indoc!(
16199        "fn a() {
16200             ˇdˇog();
16201             cat();
16202        }"
16203    ));
16204    cx.update_editor(|editor, window, cx| {
16205        editor.toggle_comments(toggle_comments, window, cx);
16206    });
16207    cx.assert_editor_state(indoc!(
16208        "fn a() {
16209             // dog();
16210             catˇ(ˇ);
16211        }"
16212    ));
16213
16214    // Multiple cursors on one line, with selection -> don't advance
16215    cx.set_state(indoc!(
16216        "fn a() {
16217             ˇdˇog«()ˇ»;
16218             cat();
16219        }"
16220    ));
16221    cx.update_editor(|editor, window, cx| {
16222        editor.toggle_comments(toggle_comments, window, cx);
16223    });
16224    cx.assert_editor_state(indoc!(
16225        "fn a() {
16226             // ˇdˇog«()ˇ»;
16227             cat();
16228        }"
16229    ));
16230
16231    // Single cursor on one line -> advance
16232    // Cursor moves to column 0 on blank line
16233    cx.set_state(indoc!(
16234        "fn a() {
16235             ˇdog();
16236
16237             cat();
16238        }"
16239    ));
16240    cx.update_editor(|editor, window, cx| {
16241        editor.toggle_comments(toggle_comments, window, cx);
16242    });
16243    cx.assert_editor_state(indoc!(
16244        "fn a() {
16245             // dog();
16246        ˇ
16247             cat();
16248        }"
16249    ));
16250
16251    // Single cursor on one line -> advance
16252    // Cursor starts and ends at column 0
16253    cx.set_state(indoc!(
16254        "fn a() {
16255         ˇ    dog();
16256             cat();
16257        }"
16258    ));
16259    cx.update_editor(|editor, window, cx| {
16260        editor.toggle_comments(toggle_comments, window, cx);
16261    });
16262    cx.assert_editor_state(indoc!(
16263        "fn a() {
16264             // dog();
16265         ˇ    cat();
16266        }"
16267    ));
16268}
16269
16270#[gpui::test]
16271async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16272    init_test(cx, |_| {});
16273
16274    let mut cx = EditorTestContext::new(cx).await;
16275
16276    let html_language = Arc::new(
16277        Language::new(
16278            LanguageConfig {
16279                name: "HTML".into(),
16280                block_comment: Some(BlockCommentConfig {
16281                    start: "<!-- ".into(),
16282                    prefix: "".into(),
16283                    end: " -->".into(),
16284                    tab_size: 0,
16285                }),
16286                ..Default::default()
16287            },
16288            Some(tree_sitter_html::LANGUAGE.into()),
16289        )
16290        .with_injection_query(
16291            r#"
16292            (script_element
16293                (raw_text) @injection.content
16294                (#set! injection.language "javascript"))
16295            "#,
16296        )
16297        .unwrap(),
16298    );
16299
16300    let javascript_language = Arc::new(Language::new(
16301        LanguageConfig {
16302            name: "JavaScript".into(),
16303            line_comments: vec!["// ".into()],
16304            ..Default::default()
16305        },
16306        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16307    ));
16308
16309    cx.language_registry().add(html_language.clone());
16310    cx.language_registry().add(javascript_language);
16311    cx.update_buffer(|buffer, cx| {
16312        buffer.set_language(Some(html_language), cx);
16313    });
16314
16315    // Toggle comments for empty selections
16316    cx.set_state(
16317        &r#"
16318            <p>A</p>ˇ
16319            <p>B</p>ˇ
16320            <p>C</p>ˇ
16321        "#
16322        .unindent(),
16323    );
16324    cx.update_editor(|editor, window, cx| {
16325        editor.toggle_comments(&ToggleComments::default(), window, cx)
16326    });
16327    cx.assert_editor_state(
16328        &r#"
16329            <!-- <p>A</p>ˇ -->
16330            <!-- <p>B</p>ˇ -->
16331            <!-- <p>C</p>ˇ -->
16332        "#
16333        .unindent(),
16334    );
16335    cx.update_editor(|editor, window, cx| {
16336        editor.toggle_comments(&ToggleComments::default(), window, cx)
16337    });
16338    cx.assert_editor_state(
16339        &r#"
16340            <p>A</p>ˇ
16341            <p>B</p>ˇ
16342            <p>C</p>ˇ
16343        "#
16344        .unindent(),
16345    );
16346
16347    // Toggle comments for mixture of empty and non-empty selections, where
16348    // multiple selections occupy a given line.
16349    cx.set_state(
16350        &r#"
16351            <p>A«</p>
16352            <p>ˇ»B</p>ˇ
16353            <p>C«</p>
16354            <p>ˇ»D</p>ˇ
16355        "#
16356        .unindent(),
16357    );
16358
16359    cx.update_editor(|editor, window, cx| {
16360        editor.toggle_comments(&ToggleComments::default(), window, cx)
16361    });
16362    cx.assert_editor_state(
16363        &r#"
16364            <!-- <p>A«</p>
16365            <p>ˇ»B</p>ˇ -->
16366            <!-- <p>C«</p>
16367            <p>ˇ»D</p>ˇ -->
16368        "#
16369        .unindent(),
16370    );
16371    cx.update_editor(|editor, window, cx| {
16372        editor.toggle_comments(&ToggleComments::default(), window, cx)
16373    });
16374    cx.assert_editor_state(
16375        &r#"
16376            <p>A«</p>
16377            <p>ˇ»B</p>ˇ
16378            <p>C«</p>
16379            <p>ˇ»D</p>ˇ
16380        "#
16381        .unindent(),
16382    );
16383
16384    // Toggle comments when different languages are active for different
16385    // selections.
16386    cx.set_state(
16387        &r#"
16388            ˇ<script>
16389                ˇvar x = new Y();
16390            ˇ</script>
16391        "#
16392        .unindent(),
16393    );
16394    cx.executor().run_until_parked();
16395    cx.update_editor(|editor, window, cx| {
16396        editor.toggle_comments(&ToggleComments::default(), window, cx)
16397    });
16398    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16399    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16400    cx.assert_editor_state(
16401        &r#"
16402            <!-- ˇ<script> -->
16403                // ˇvar x = new Y();
16404            <!-- ˇ</script> -->
16405        "#
16406        .unindent(),
16407    );
16408}
16409
16410#[gpui::test]
16411fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16412    init_test(cx, |_| {});
16413
16414    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16415    let multibuffer = cx.new(|cx| {
16416        let mut multibuffer = MultiBuffer::new(ReadWrite);
16417        multibuffer.push_excerpts(
16418            buffer.clone(),
16419            [
16420                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16421                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16422            ],
16423            cx,
16424        );
16425        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16426        multibuffer
16427    });
16428
16429    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16430    editor.update_in(cx, |editor, window, cx| {
16431        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16433            s.select_ranges([
16434                Point::new(0, 0)..Point::new(0, 0),
16435                Point::new(1, 0)..Point::new(1, 0),
16436            ])
16437        });
16438
16439        editor.handle_input("X", window, cx);
16440        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16441        assert_eq!(
16442            editor.selections.ranges(&editor.display_snapshot(cx)),
16443            [
16444                Point::new(0, 1)..Point::new(0, 1),
16445                Point::new(1, 1)..Point::new(1, 1),
16446            ]
16447        );
16448
16449        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16451            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16452        });
16453        editor.backspace(&Default::default(), window, cx);
16454        assert_eq!(editor.text(cx), "Xa\nbbb");
16455        assert_eq!(
16456            editor.selections.ranges(&editor.display_snapshot(cx)),
16457            [Point::new(1, 0)..Point::new(1, 0)]
16458        );
16459
16460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16461            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16462        });
16463        editor.backspace(&Default::default(), window, cx);
16464        assert_eq!(editor.text(cx), "X\nbb");
16465        assert_eq!(
16466            editor.selections.ranges(&editor.display_snapshot(cx)),
16467            [Point::new(0, 1)..Point::new(0, 1)]
16468        );
16469    });
16470}
16471
16472#[gpui::test]
16473fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16474    init_test(cx, |_| {});
16475
16476    let markers = vec![('[', ']').into(), ('(', ')').into()];
16477    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16478        indoc! {"
16479            [aaaa
16480            (bbbb]
16481            cccc)",
16482        },
16483        markers.clone(),
16484    );
16485    let excerpt_ranges = markers.into_iter().map(|marker| {
16486        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16487        ExcerptRange::new(context)
16488    });
16489    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16490    let multibuffer = cx.new(|cx| {
16491        let mut multibuffer = MultiBuffer::new(ReadWrite);
16492        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16493        multibuffer
16494    });
16495
16496    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16497    editor.update_in(cx, |editor, window, cx| {
16498        let (expected_text, selection_ranges) = marked_text_ranges(
16499            indoc! {"
16500                aaaa
16501                bˇbbb
16502                bˇbbˇb
16503                cccc"
16504            },
16505            true,
16506        );
16507        assert_eq!(editor.text(cx), expected_text);
16508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16509            s.select_ranges(
16510                selection_ranges
16511                    .iter()
16512                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16513            )
16514        });
16515
16516        editor.handle_input("X", window, cx);
16517
16518        let (expected_text, expected_selections) = marked_text_ranges(
16519            indoc! {"
16520                aaaa
16521                bXˇbbXb
16522                bXˇbbXˇb
16523                cccc"
16524            },
16525            false,
16526        );
16527        assert_eq!(editor.text(cx), expected_text);
16528        assert_eq!(
16529            editor.selections.ranges(&editor.display_snapshot(cx)),
16530            expected_selections
16531                .iter()
16532                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16533                .collect::<Vec<_>>()
16534        );
16535
16536        editor.newline(&Newline, window, cx);
16537        let (expected_text, expected_selections) = marked_text_ranges(
16538            indoc! {"
16539                aaaa
16540                bX
16541                ˇbbX
16542                b
16543                bX
16544                ˇbbX
16545                ˇb
16546                cccc"
16547            },
16548            false,
16549        );
16550        assert_eq!(editor.text(cx), expected_text);
16551        assert_eq!(
16552            editor.selections.ranges(&editor.display_snapshot(cx)),
16553            expected_selections
16554                .iter()
16555                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16556                .collect::<Vec<_>>()
16557        );
16558    });
16559}
16560
16561#[gpui::test]
16562fn test_refresh_selections(cx: &mut TestAppContext) {
16563    init_test(cx, |_| {});
16564
16565    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16566    let mut excerpt1_id = None;
16567    let multibuffer = cx.new(|cx| {
16568        let mut multibuffer = MultiBuffer::new(ReadWrite);
16569        excerpt1_id = multibuffer
16570            .push_excerpts(
16571                buffer.clone(),
16572                [
16573                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16574                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16575                ],
16576                cx,
16577            )
16578            .into_iter()
16579            .next();
16580        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16581        multibuffer
16582    });
16583
16584    let editor = cx.add_window(|window, cx| {
16585        let mut editor = build_editor(multibuffer.clone(), window, cx);
16586        let snapshot = editor.snapshot(window, cx);
16587        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16588            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16589        });
16590        editor.begin_selection(
16591            Point::new(2, 1).to_display_point(&snapshot),
16592            true,
16593            1,
16594            window,
16595            cx,
16596        );
16597        assert_eq!(
16598            editor.selections.ranges(&editor.display_snapshot(cx)),
16599            [
16600                Point::new(1, 3)..Point::new(1, 3),
16601                Point::new(2, 1)..Point::new(2, 1),
16602            ]
16603        );
16604        editor
16605    });
16606
16607    // Refreshing selections is a no-op when excerpts haven't changed.
16608    _ = editor.update(cx, |editor, window, cx| {
16609        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16610        assert_eq!(
16611            editor.selections.ranges(&editor.display_snapshot(cx)),
16612            [
16613                Point::new(1, 3)..Point::new(1, 3),
16614                Point::new(2, 1)..Point::new(2, 1),
16615            ]
16616        );
16617    });
16618
16619    multibuffer.update(cx, |multibuffer, cx| {
16620        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16621    });
16622    _ = editor.update(cx, |editor, window, cx| {
16623        // Removing an excerpt causes the first selection to become degenerate.
16624        assert_eq!(
16625            editor.selections.ranges(&editor.display_snapshot(cx)),
16626            [
16627                Point::new(0, 0)..Point::new(0, 0),
16628                Point::new(0, 1)..Point::new(0, 1)
16629            ]
16630        );
16631
16632        // Refreshing selections will relocate the first selection to the original buffer
16633        // location.
16634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16635        assert_eq!(
16636            editor.selections.ranges(&editor.display_snapshot(cx)),
16637            [
16638                Point::new(0, 1)..Point::new(0, 1),
16639                Point::new(0, 3)..Point::new(0, 3)
16640            ]
16641        );
16642        assert!(editor.selections.pending_anchor().is_some());
16643    });
16644}
16645
16646#[gpui::test]
16647fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16648    init_test(cx, |_| {});
16649
16650    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16651    let mut excerpt1_id = None;
16652    let multibuffer = cx.new(|cx| {
16653        let mut multibuffer = MultiBuffer::new(ReadWrite);
16654        excerpt1_id = multibuffer
16655            .push_excerpts(
16656                buffer.clone(),
16657                [
16658                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16659                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16660                ],
16661                cx,
16662            )
16663            .into_iter()
16664            .next();
16665        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16666        multibuffer
16667    });
16668
16669    let editor = cx.add_window(|window, cx| {
16670        let mut editor = build_editor(multibuffer.clone(), window, cx);
16671        let snapshot = editor.snapshot(window, cx);
16672        editor.begin_selection(
16673            Point::new(1, 3).to_display_point(&snapshot),
16674            false,
16675            1,
16676            window,
16677            cx,
16678        );
16679        assert_eq!(
16680            editor.selections.ranges(&editor.display_snapshot(cx)),
16681            [Point::new(1, 3)..Point::new(1, 3)]
16682        );
16683        editor
16684    });
16685
16686    multibuffer.update(cx, |multibuffer, cx| {
16687        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16688    });
16689    _ = editor.update(cx, |editor, window, cx| {
16690        assert_eq!(
16691            editor.selections.ranges(&editor.display_snapshot(cx)),
16692            [Point::new(0, 0)..Point::new(0, 0)]
16693        );
16694
16695        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16696        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16697        assert_eq!(
16698            editor.selections.ranges(&editor.display_snapshot(cx)),
16699            [Point::new(0, 3)..Point::new(0, 3)]
16700        );
16701        assert!(editor.selections.pending_anchor().is_some());
16702    });
16703}
16704
16705#[gpui::test]
16706async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16707    init_test(cx, |_| {});
16708
16709    let language = Arc::new(
16710        Language::new(
16711            LanguageConfig {
16712                brackets: BracketPairConfig {
16713                    pairs: vec![
16714                        BracketPair {
16715                            start: "{".to_string(),
16716                            end: "}".to_string(),
16717                            close: true,
16718                            surround: true,
16719                            newline: true,
16720                        },
16721                        BracketPair {
16722                            start: "/* ".to_string(),
16723                            end: " */".to_string(),
16724                            close: true,
16725                            surround: true,
16726                            newline: true,
16727                        },
16728                    ],
16729                    ..Default::default()
16730                },
16731                ..Default::default()
16732            },
16733            Some(tree_sitter_rust::LANGUAGE.into()),
16734        )
16735        .with_indents_query("")
16736        .unwrap(),
16737    );
16738
16739    let text = concat!(
16740        "{   }\n",     //
16741        "  x\n",       //
16742        "  /*   */\n", //
16743        "x\n",         //
16744        "{{} }\n",     //
16745    );
16746
16747    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16748    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16749    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16750    editor
16751        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16752        .await;
16753
16754    editor.update_in(cx, |editor, window, cx| {
16755        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16756            s.select_display_ranges([
16757                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16758                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16759                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16760            ])
16761        });
16762        editor.newline(&Newline, window, cx);
16763
16764        assert_eq!(
16765            editor.buffer().read(cx).read(cx).text(),
16766            concat!(
16767                "{ \n",    // Suppress rustfmt
16768                "\n",      //
16769                "}\n",     //
16770                "  x\n",   //
16771                "  /* \n", //
16772                "  \n",    //
16773                "  */\n",  //
16774                "x\n",     //
16775                "{{} \n",  //
16776                "}\n",     //
16777            )
16778        );
16779    });
16780}
16781
16782#[gpui::test]
16783fn test_highlighted_ranges(cx: &mut TestAppContext) {
16784    init_test(cx, |_| {});
16785
16786    let editor = cx.add_window(|window, cx| {
16787        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16788        build_editor(buffer, window, cx)
16789    });
16790
16791    _ = editor.update(cx, |editor, window, cx| {
16792        struct Type1;
16793        struct Type2;
16794
16795        let buffer = editor.buffer.read(cx).snapshot(cx);
16796
16797        let anchor_range =
16798            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16799
16800        editor.highlight_background::<Type1>(
16801            &[
16802                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16803                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16804                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16805                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16806            ],
16807            |_| Hsla::red(),
16808            cx,
16809        );
16810        editor.highlight_background::<Type2>(
16811            &[
16812                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16813                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16814                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16815                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16816            ],
16817            |_| Hsla::green(),
16818            cx,
16819        );
16820
16821        let snapshot = editor.snapshot(window, cx);
16822        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16823            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16824            &snapshot,
16825            cx.theme(),
16826        );
16827        assert_eq!(
16828            highlighted_ranges,
16829            &[
16830                (
16831                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16832                    Hsla::green(),
16833                ),
16834                (
16835                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16836                    Hsla::red(),
16837                ),
16838                (
16839                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16840                    Hsla::green(),
16841                ),
16842                (
16843                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16844                    Hsla::red(),
16845                ),
16846            ]
16847        );
16848        assert_eq!(
16849            editor.sorted_background_highlights_in_range(
16850                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16851                &snapshot,
16852                cx.theme(),
16853            ),
16854            &[(
16855                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16856                Hsla::red(),
16857            )]
16858        );
16859    });
16860}
16861
16862#[gpui::test]
16863async fn test_following(cx: &mut TestAppContext) {
16864    init_test(cx, |_| {});
16865
16866    let fs = FakeFs::new(cx.executor());
16867    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16868
16869    let buffer = project.update(cx, |project, cx| {
16870        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16871        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16872    });
16873    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16874    let follower = cx.update(|cx| {
16875        cx.open_window(
16876            WindowOptions {
16877                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16878                    gpui::Point::new(px(0.), px(0.)),
16879                    gpui::Point::new(px(10.), px(80.)),
16880                ))),
16881                ..Default::default()
16882            },
16883            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16884        )
16885        .unwrap()
16886    });
16887
16888    let is_still_following = Rc::new(RefCell::new(true));
16889    let follower_edit_event_count = Rc::new(RefCell::new(0));
16890    let pending_update = Rc::new(RefCell::new(None));
16891    let leader_entity = leader.root(cx).unwrap();
16892    let follower_entity = follower.root(cx).unwrap();
16893    _ = follower.update(cx, {
16894        let update = pending_update.clone();
16895        let is_still_following = is_still_following.clone();
16896        let follower_edit_event_count = follower_edit_event_count.clone();
16897        |_, window, cx| {
16898            cx.subscribe_in(
16899                &leader_entity,
16900                window,
16901                move |_, leader, event, window, cx| {
16902                    leader.read(cx).add_event_to_update_proto(
16903                        event,
16904                        &mut update.borrow_mut(),
16905                        window,
16906                        cx,
16907                    );
16908                },
16909            )
16910            .detach();
16911
16912            cx.subscribe_in(
16913                &follower_entity,
16914                window,
16915                move |_, _, event: &EditorEvent, _window, _cx| {
16916                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16917                        *is_still_following.borrow_mut() = false;
16918                    }
16919
16920                    if let EditorEvent::BufferEdited = event {
16921                        *follower_edit_event_count.borrow_mut() += 1;
16922                    }
16923                },
16924            )
16925            .detach();
16926        }
16927    });
16928
16929    // Update the selections only
16930    _ = leader.update(cx, |leader, window, cx| {
16931        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16932            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
16933        });
16934    });
16935    follower
16936        .update(cx, |follower, window, cx| {
16937            follower.apply_update_proto(
16938                &project,
16939                pending_update.borrow_mut().take().unwrap(),
16940                window,
16941                cx,
16942            )
16943        })
16944        .unwrap()
16945        .await
16946        .unwrap();
16947    _ = follower.update(cx, |follower, _, cx| {
16948        assert_eq!(
16949            follower.selections.ranges(&follower.display_snapshot(cx)),
16950            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
16951        );
16952    });
16953    assert!(*is_still_following.borrow());
16954    assert_eq!(*follower_edit_event_count.borrow(), 0);
16955
16956    // Update the scroll position only
16957    _ = leader.update(cx, |leader, window, cx| {
16958        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16959    });
16960    follower
16961        .update(cx, |follower, window, cx| {
16962            follower.apply_update_proto(
16963                &project,
16964                pending_update.borrow_mut().take().unwrap(),
16965                window,
16966                cx,
16967            )
16968        })
16969        .unwrap()
16970        .await
16971        .unwrap();
16972    assert_eq!(
16973        follower
16974            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16975            .unwrap(),
16976        gpui::Point::new(1.5, 3.5)
16977    );
16978    assert!(*is_still_following.borrow());
16979    assert_eq!(*follower_edit_event_count.borrow(), 0);
16980
16981    // Update the selections and scroll position. The follower's scroll position is updated
16982    // via autoscroll, not via the leader's exact scroll position.
16983    _ = leader.update(cx, |leader, window, cx| {
16984        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16985            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
16986        });
16987        leader.request_autoscroll(Autoscroll::newest(), cx);
16988        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16989    });
16990    follower
16991        .update(cx, |follower, window, cx| {
16992            follower.apply_update_proto(
16993                &project,
16994                pending_update.borrow_mut().take().unwrap(),
16995                window,
16996                cx,
16997            )
16998        })
16999        .unwrap()
17000        .await
17001        .unwrap();
17002    _ = follower.update(cx, |follower, _, cx| {
17003        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17004        assert_eq!(
17005            follower.selections.ranges(&follower.display_snapshot(cx)),
17006            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17007        );
17008    });
17009    assert!(*is_still_following.borrow());
17010
17011    // Creating a pending selection that precedes another selection
17012    _ = leader.update(cx, |leader, window, cx| {
17013        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17014            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17015        });
17016        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17017    });
17018    follower
17019        .update(cx, |follower, window, cx| {
17020            follower.apply_update_proto(
17021                &project,
17022                pending_update.borrow_mut().take().unwrap(),
17023                window,
17024                cx,
17025            )
17026        })
17027        .unwrap()
17028        .await
17029        .unwrap();
17030    _ = follower.update(cx, |follower, _, cx| {
17031        assert_eq!(
17032            follower.selections.ranges(&follower.display_snapshot(cx)),
17033            vec![
17034                MultiBufferOffset(0)..MultiBufferOffset(0),
17035                MultiBufferOffset(1)..MultiBufferOffset(1)
17036            ]
17037        );
17038    });
17039    assert!(*is_still_following.borrow());
17040
17041    // Extend the pending selection so that it surrounds another selection
17042    _ = leader.update(cx, |leader, window, cx| {
17043        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17044    });
17045    follower
17046        .update(cx, |follower, window, cx| {
17047            follower.apply_update_proto(
17048                &project,
17049                pending_update.borrow_mut().take().unwrap(),
17050                window,
17051                cx,
17052            )
17053        })
17054        .unwrap()
17055        .await
17056        .unwrap();
17057    _ = follower.update(cx, |follower, _, cx| {
17058        assert_eq!(
17059            follower.selections.ranges(&follower.display_snapshot(cx)),
17060            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17061        );
17062    });
17063
17064    // Scrolling locally breaks the follow
17065    _ = follower.update(cx, |follower, window, cx| {
17066        let top_anchor = follower
17067            .buffer()
17068            .read(cx)
17069            .read(cx)
17070            .anchor_after(MultiBufferOffset(0));
17071        follower.set_scroll_anchor(
17072            ScrollAnchor {
17073                anchor: top_anchor,
17074                offset: gpui::Point::new(0.0, 0.5),
17075            },
17076            window,
17077            cx,
17078        );
17079    });
17080    assert!(!(*is_still_following.borrow()));
17081}
17082
17083#[gpui::test]
17084async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17085    init_test(cx, |_| {});
17086
17087    let fs = FakeFs::new(cx.executor());
17088    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17089    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17090    let pane = workspace
17091        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17092        .unwrap();
17093
17094    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17095
17096    let leader = pane.update_in(cx, |_, window, cx| {
17097        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17098        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17099    });
17100
17101    // Start following the editor when it has no excerpts.
17102    let mut state_message =
17103        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17104    let workspace_entity = workspace.root(cx).unwrap();
17105    let follower_1 = cx
17106        .update_window(*workspace.deref(), |_, window, cx| {
17107            Editor::from_state_proto(
17108                workspace_entity,
17109                ViewId {
17110                    creator: CollaboratorId::PeerId(PeerId::default()),
17111                    id: 0,
17112                },
17113                &mut state_message,
17114                window,
17115                cx,
17116            )
17117        })
17118        .unwrap()
17119        .unwrap()
17120        .await
17121        .unwrap();
17122
17123    let update_message = Rc::new(RefCell::new(None));
17124    follower_1.update_in(cx, {
17125        let update = update_message.clone();
17126        |_, window, cx| {
17127            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17128                leader.read(cx).add_event_to_update_proto(
17129                    event,
17130                    &mut update.borrow_mut(),
17131                    window,
17132                    cx,
17133                );
17134            })
17135            .detach();
17136        }
17137    });
17138
17139    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17140        (
17141            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17142            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17143        )
17144    });
17145
17146    // Insert some excerpts.
17147    leader.update(cx, |leader, cx| {
17148        leader.buffer.update(cx, |multibuffer, cx| {
17149            multibuffer.set_excerpts_for_path(
17150                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17151                buffer_1.clone(),
17152                vec![
17153                    Point::row_range(0..3),
17154                    Point::row_range(1..6),
17155                    Point::row_range(12..15),
17156                ],
17157                0,
17158                cx,
17159            );
17160            multibuffer.set_excerpts_for_path(
17161                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17162                buffer_2.clone(),
17163                vec![Point::row_range(0..6), Point::row_range(8..12)],
17164                0,
17165                cx,
17166            );
17167        });
17168    });
17169
17170    // Apply the update of adding the excerpts.
17171    follower_1
17172        .update_in(cx, |follower, window, cx| {
17173            follower.apply_update_proto(
17174                &project,
17175                update_message.borrow().clone().unwrap(),
17176                window,
17177                cx,
17178            )
17179        })
17180        .await
17181        .unwrap();
17182    assert_eq!(
17183        follower_1.update(cx, |editor, cx| editor.text(cx)),
17184        leader.update(cx, |editor, cx| editor.text(cx))
17185    );
17186    update_message.borrow_mut().take();
17187
17188    // Start following separately after it already has excerpts.
17189    let mut state_message =
17190        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17191    let workspace_entity = workspace.root(cx).unwrap();
17192    let follower_2 = cx
17193        .update_window(*workspace.deref(), |_, window, cx| {
17194            Editor::from_state_proto(
17195                workspace_entity,
17196                ViewId {
17197                    creator: CollaboratorId::PeerId(PeerId::default()),
17198                    id: 0,
17199                },
17200                &mut state_message,
17201                window,
17202                cx,
17203            )
17204        })
17205        .unwrap()
17206        .unwrap()
17207        .await
17208        .unwrap();
17209    assert_eq!(
17210        follower_2.update(cx, |editor, cx| editor.text(cx)),
17211        leader.update(cx, |editor, cx| editor.text(cx))
17212    );
17213
17214    // Remove some excerpts.
17215    leader.update(cx, |leader, cx| {
17216        leader.buffer.update(cx, |multibuffer, cx| {
17217            let excerpt_ids = multibuffer.excerpt_ids();
17218            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17219            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17220        });
17221    });
17222
17223    // Apply the update of removing the excerpts.
17224    follower_1
17225        .update_in(cx, |follower, window, cx| {
17226            follower.apply_update_proto(
17227                &project,
17228                update_message.borrow().clone().unwrap(),
17229                window,
17230                cx,
17231            )
17232        })
17233        .await
17234        .unwrap();
17235    follower_2
17236        .update_in(cx, |follower, window, cx| {
17237            follower.apply_update_proto(
17238                &project,
17239                update_message.borrow().clone().unwrap(),
17240                window,
17241                cx,
17242            )
17243        })
17244        .await
17245        .unwrap();
17246    update_message.borrow_mut().take();
17247    assert_eq!(
17248        follower_1.update(cx, |editor, cx| editor.text(cx)),
17249        leader.update(cx, |editor, cx| editor.text(cx))
17250    );
17251}
17252
17253#[gpui::test]
17254async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17255    init_test(cx, |_| {});
17256
17257    let mut cx = EditorTestContext::new(cx).await;
17258    let lsp_store =
17259        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17260
17261    cx.set_state(indoc! {"
17262        ˇfn func(abc def: i32) -> u32 {
17263        }
17264    "});
17265
17266    cx.update(|_, cx| {
17267        lsp_store.update(cx, |lsp_store, cx| {
17268            lsp_store
17269                .update_diagnostics(
17270                    LanguageServerId(0),
17271                    lsp::PublishDiagnosticsParams {
17272                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17273                        version: None,
17274                        diagnostics: vec![
17275                            lsp::Diagnostic {
17276                                range: lsp::Range::new(
17277                                    lsp::Position::new(0, 11),
17278                                    lsp::Position::new(0, 12),
17279                                ),
17280                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17281                                ..Default::default()
17282                            },
17283                            lsp::Diagnostic {
17284                                range: lsp::Range::new(
17285                                    lsp::Position::new(0, 12),
17286                                    lsp::Position::new(0, 15),
17287                                ),
17288                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17289                                ..Default::default()
17290                            },
17291                            lsp::Diagnostic {
17292                                range: lsp::Range::new(
17293                                    lsp::Position::new(0, 25),
17294                                    lsp::Position::new(0, 28),
17295                                ),
17296                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17297                                ..Default::default()
17298                            },
17299                        ],
17300                    },
17301                    None,
17302                    DiagnosticSourceKind::Pushed,
17303                    &[],
17304                    cx,
17305                )
17306                .unwrap()
17307        });
17308    });
17309
17310    executor.run_until_parked();
17311
17312    cx.update_editor(|editor, window, cx| {
17313        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17314    });
17315
17316    cx.assert_editor_state(indoc! {"
17317        fn func(abc def: i32) -> ˇu32 {
17318        }
17319    "});
17320
17321    cx.update_editor(|editor, window, cx| {
17322        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17323    });
17324
17325    cx.assert_editor_state(indoc! {"
17326        fn func(abc ˇdef: i32) -> u32 {
17327        }
17328    "});
17329
17330    cx.update_editor(|editor, window, cx| {
17331        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17332    });
17333
17334    cx.assert_editor_state(indoc! {"
17335        fn func(abcˇ def: i32) -> u32 {
17336        }
17337    "});
17338
17339    cx.update_editor(|editor, window, cx| {
17340        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17341    });
17342
17343    cx.assert_editor_state(indoc! {"
17344        fn func(abc def: i32) -> ˇu32 {
17345        }
17346    "});
17347}
17348
17349#[gpui::test]
17350async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17351    init_test(cx, |_| {});
17352
17353    let mut cx = EditorTestContext::new(cx).await;
17354
17355    let diff_base = r#"
17356        use some::mod;
17357
17358        const A: u32 = 42;
17359
17360        fn main() {
17361            println!("hello");
17362
17363            println!("world");
17364        }
17365        "#
17366    .unindent();
17367
17368    // Edits are modified, removed, modified, added
17369    cx.set_state(
17370        &r#"
17371        use some::modified;
17372
17373        ˇ
17374        fn main() {
17375            println!("hello there");
17376
17377            println!("around the");
17378            println!("world");
17379        }
17380        "#
17381        .unindent(),
17382    );
17383
17384    cx.set_head_text(&diff_base);
17385    executor.run_until_parked();
17386
17387    cx.update_editor(|editor, window, cx| {
17388        //Wrap around the bottom of the buffer
17389        for _ in 0..3 {
17390            editor.go_to_next_hunk(&GoToHunk, window, cx);
17391        }
17392    });
17393
17394    cx.assert_editor_state(
17395        &r#"
17396        ˇuse some::modified;
17397
17398
17399        fn main() {
17400            println!("hello there");
17401
17402            println!("around the");
17403            println!("world");
17404        }
17405        "#
17406        .unindent(),
17407    );
17408
17409    cx.update_editor(|editor, window, cx| {
17410        //Wrap around the top of the buffer
17411        for _ in 0..2 {
17412            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17413        }
17414    });
17415
17416    cx.assert_editor_state(
17417        &r#"
17418        use some::modified;
17419
17420
17421        fn main() {
17422        ˇ    println!("hello there");
17423
17424            println!("around the");
17425            println!("world");
17426        }
17427        "#
17428        .unindent(),
17429    );
17430
17431    cx.update_editor(|editor, window, cx| {
17432        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17433    });
17434
17435    cx.assert_editor_state(
17436        &r#"
17437        use some::modified;
17438
17439        ˇ
17440        fn main() {
17441            println!("hello there");
17442
17443            println!("around the");
17444            println!("world");
17445        }
17446        "#
17447        .unindent(),
17448    );
17449
17450    cx.update_editor(|editor, window, cx| {
17451        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17452    });
17453
17454    cx.assert_editor_state(
17455        &r#"
17456        ˇuse some::modified;
17457
17458
17459        fn main() {
17460            println!("hello there");
17461
17462            println!("around the");
17463            println!("world");
17464        }
17465        "#
17466        .unindent(),
17467    );
17468
17469    cx.update_editor(|editor, window, cx| {
17470        for _ in 0..2 {
17471            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17472        }
17473    });
17474
17475    cx.assert_editor_state(
17476        &r#"
17477        use some::modified;
17478
17479
17480        fn main() {
17481        ˇ    println!("hello there");
17482
17483            println!("around the");
17484            println!("world");
17485        }
17486        "#
17487        .unindent(),
17488    );
17489
17490    cx.update_editor(|editor, window, cx| {
17491        editor.fold(&Fold, window, cx);
17492    });
17493
17494    cx.update_editor(|editor, window, cx| {
17495        editor.go_to_next_hunk(&GoToHunk, window, cx);
17496    });
17497
17498    cx.assert_editor_state(
17499        &r#"
17500        ˇuse some::modified;
17501
17502
17503        fn main() {
17504            println!("hello there");
17505
17506            println!("around the");
17507            println!("world");
17508        }
17509        "#
17510        .unindent(),
17511    );
17512}
17513
17514#[test]
17515fn test_split_words() {
17516    fn split(text: &str) -> Vec<&str> {
17517        split_words(text).collect()
17518    }
17519
17520    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17521    assert_eq!(split("hello_world"), &["hello_", "world"]);
17522    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17523    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17524    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17525    assert_eq!(split("helloworld"), &["helloworld"]);
17526
17527    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17528}
17529
17530#[test]
17531fn test_split_words_for_snippet_prefix() {
17532    fn split(text: &str) -> Vec<&str> {
17533        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17534    }
17535
17536    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17537    assert_eq!(split("hello_world"), &["hello_world"]);
17538    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17539    assert_eq!(split("Hello_World"), &["Hello_World"]);
17540    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17541    assert_eq!(split("helloworld"), &["helloworld"]);
17542    assert_eq!(
17543        split("this@is!@#$^many   . symbols"),
17544        &[
17545            "symbols",
17546            " symbols",
17547            ". symbols",
17548            " . symbols",
17549            "  . symbols",
17550            "   . symbols",
17551            "many   . symbols",
17552            "^many   . symbols",
17553            "$^many   . symbols",
17554            "#$^many   . symbols",
17555            "@#$^many   . symbols",
17556            "!@#$^many   . symbols",
17557            "is!@#$^many   . symbols",
17558            "@is!@#$^many   . symbols",
17559            "this@is!@#$^many   . symbols",
17560        ],
17561    );
17562    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17563}
17564
17565#[gpui::test]
17566async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17567    init_test(cx, |_| {});
17568
17569    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17570
17571    #[track_caller]
17572    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17573        let _state_context = cx.set_state(before);
17574        cx.run_until_parked();
17575        cx.update_editor(|editor, window, cx| {
17576            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17577        });
17578        cx.run_until_parked();
17579        cx.assert_editor_state(after);
17580    }
17581
17582    // Outside bracket jumps to outside of matching bracket
17583    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17584    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17585
17586    // Inside bracket jumps to inside of matching bracket
17587    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17588    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17589
17590    // When outside a bracket and inside, favor jumping to the inside bracket
17591    assert(
17592        "console.log('foo', [1, 2, 3]ˇ);",
17593        "console.log('foo', ˇ[1, 2, 3]);",
17594        &mut cx,
17595    );
17596    assert(
17597        "console.log(ˇ'foo', [1, 2, 3]);",
17598        "console.log('foo'ˇ, [1, 2, 3]);",
17599        &mut cx,
17600    );
17601
17602    // Bias forward if two options are equally likely
17603    assert(
17604        "let result = curried_fun()ˇ();",
17605        "let result = curried_fun()()ˇ;",
17606        &mut cx,
17607    );
17608
17609    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17610    assert(
17611        indoc! {"
17612            function test() {
17613                console.log('test')ˇ
17614            }"},
17615        indoc! {"
17616            function test() {
17617                console.logˇ('test')
17618            }"},
17619        &mut cx,
17620    );
17621}
17622
17623#[gpui::test]
17624async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
17625    init_test(cx, |_| {});
17626    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
17627    language_registry.add(markdown_lang());
17628    language_registry.add(rust_lang());
17629    let buffer = cx.new(|cx| {
17630        let mut buffer = language::Buffer::local(
17631            indoc! {"
17632            ```rs
17633            impl Worktree {
17634                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17635                }
17636            }
17637            ```
17638        "},
17639            cx,
17640        );
17641        buffer.set_language_registry(language_registry.clone());
17642        buffer.set_language(Some(markdown_lang()), cx);
17643        buffer
17644    });
17645    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17646    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17647    cx.executor().run_until_parked();
17648    _ = editor.update(cx, |editor, window, cx| {
17649        // Case 1: Test outer enclosing brackets
17650        select_ranges(
17651            editor,
17652            &indoc! {"
17653                ```rs
17654                impl Worktree {
17655                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17656                    }
1765717658                ```
17659            "},
17660            window,
17661            cx,
17662        );
17663        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17664        assert_text_with_selections(
17665            editor,
17666            &indoc! {"
17667                ```rs
17668                impl Worktree ˇ{
17669                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17670                    }
17671                }
17672                ```
17673            "},
17674            cx,
17675        );
17676        // Case 2: Test inner enclosing brackets
17677        select_ranges(
17678            editor,
17679            &indoc! {"
17680                ```rs
17681                impl Worktree {
17682                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1768317684                }
17685                ```
17686            "},
17687            window,
17688            cx,
17689        );
17690        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17691        assert_text_with_selections(
17692            editor,
17693            &indoc! {"
17694                ```rs
17695                impl Worktree {
17696                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
17697                    }
17698                }
17699                ```
17700            "},
17701            cx,
17702        );
17703    });
17704}
17705
17706#[gpui::test]
17707async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17708    init_test(cx, |_| {});
17709
17710    let fs = FakeFs::new(cx.executor());
17711    fs.insert_tree(
17712        path!("/a"),
17713        json!({
17714            "main.rs": "fn main() { let a = 5; }",
17715            "other.rs": "// Test file",
17716        }),
17717    )
17718    .await;
17719    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17720
17721    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17722    language_registry.add(Arc::new(Language::new(
17723        LanguageConfig {
17724            name: "Rust".into(),
17725            matcher: LanguageMatcher {
17726                path_suffixes: vec!["rs".to_string()],
17727                ..Default::default()
17728            },
17729            brackets: BracketPairConfig {
17730                pairs: vec![BracketPair {
17731                    start: "{".to_string(),
17732                    end: "}".to_string(),
17733                    close: true,
17734                    surround: true,
17735                    newline: true,
17736                }],
17737                disabled_scopes_by_bracket_ix: Vec::new(),
17738            },
17739            ..Default::default()
17740        },
17741        Some(tree_sitter_rust::LANGUAGE.into()),
17742    )));
17743    let mut fake_servers = language_registry.register_fake_lsp(
17744        "Rust",
17745        FakeLspAdapter {
17746            capabilities: lsp::ServerCapabilities {
17747                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17748                    first_trigger_character: "{".to_string(),
17749                    more_trigger_character: None,
17750                }),
17751                ..Default::default()
17752            },
17753            ..Default::default()
17754        },
17755    );
17756
17757    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17758
17759    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17760
17761    let worktree_id = workspace
17762        .update(cx, |workspace, _, cx| {
17763            workspace.project().update(cx, |project, cx| {
17764                project.worktrees(cx).next().unwrap().read(cx).id()
17765            })
17766        })
17767        .unwrap();
17768
17769    let buffer = project
17770        .update(cx, |project, cx| {
17771            project.open_local_buffer(path!("/a/main.rs"), cx)
17772        })
17773        .await
17774        .unwrap();
17775    let editor_handle = workspace
17776        .update(cx, |workspace, window, cx| {
17777            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17778        })
17779        .unwrap()
17780        .await
17781        .unwrap()
17782        .downcast::<Editor>()
17783        .unwrap();
17784
17785    cx.executor().start_waiting();
17786    let fake_server = fake_servers.next().await.unwrap();
17787
17788    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17789        |params, _| async move {
17790            assert_eq!(
17791                params.text_document_position.text_document.uri,
17792                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17793            );
17794            assert_eq!(
17795                params.text_document_position.position,
17796                lsp::Position::new(0, 21),
17797            );
17798
17799            Ok(Some(vec![lsp::TextEdit {
17800                new_text: "]".to_string(),
17801                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17802            }]))
17803        },
17804    );
17805
17806    editor_handle.update_in(cx, |editor, window, cx| {
17807        window.focus(&editor.focus_handle(cx));
17808        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17809            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17810        });
17811        editor.handle_input("{", window, cx);
17812    });
17813
17814    cx.executor().run_until_parked();
17815
17816    buffer.update(cx, |buffer, _| {
17817        assert_eq!(
17818            buffer.text(),
17819            "fn main() { let a = {5}; }",
17820            "No extra braces from on type formatting should appear in the buffer"
17821        )
17822    });
17823}
17824
17825#[gpui::test(iterations = 20, seeds(31))]
17826async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17827    init_test(cx, |_| {});
17828
17829    let mut cx = EditorLspTestContext::new_rust(
17830        lsp::ServerCapabilities {
17831            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17832                first_trigger_character: ".".to_string(),
17833                more_trigger_character: None,
17834            }),
17835            ..Default::default()
17836        },
17837        cx,
17838    )
17839    .await;
17840
17841    cx.update_buffer(|buffer, _| {
17842        // This causes autoindent to be async.
17843        buffer.set_sync_parse_timeout(Duration::ZERO)
17844    });
17845
17846    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17847    cx.simulate_keystroke("\n");
17848    cx.run_until_parked();
17849
17850    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17851    let mut request =
17852        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17853            let buffer_cloned = buffer_cloned.clone();
17854            async move {
17855                buffer_cloned.update(&mut cx, |buffer, _| {
17856                    assert_eq!(
17857                        buffer.text(),
17858                        "fn c() {\n    d()\n        .\n}\n",
17859                        "OnTypeFormatting should triggered after autoindent applied"
17860                    )
17861                })?;
17862
17863                Ok(Some(vec![]))
17864            }
17865        });
17866
17867    cx.simulate_keystroke(".");
17868    cx.run_until_parked();
17869
17870    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17871    assert!(request.next().await.is_some());
17872    request.close();
17873    assert!(request.next().await.is_none());
17874}
17875
17876#[gpui::test]
17877async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17878    init_test(cx, |_| {});
17879
17880    let fs = FakeFs::new(cx.executor());
17881    fs.insert_tree(
17882        path!("/a"),
17883        json!({
17884            "main.rs": "fn main() { let a = 5; }",
17885            "other.rs": "// Test file",
17886        }),
17887    )
17888    .await;
17889
17890    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17891
17892    let server_restarts = Arc::new(AtomicUsize::new(0));
17893    let closure_restarts = Arc::clone(&server_restarts);
17894    let language_server_name = "test language server";
17895    let language_name: LanguageName = "Rust".into();
17896
17897    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17898    language_registry.add(Arc::new(Language::new(
17899        LanguageConfig {
17900            name: language_name.clone(),
17901            matcher: LanguageMatcher {
17902                path_suffixes: vec!["rs".to_string()],
17903                ..Default::default()
17904            },
17905            ..Default::default()
17906        },
17907        Some(tree_sitter_rust::LANGUAGE.into()),
17908    )));
17909    let mut fake_servers = language_registry.register_fake_lsp(
17910        "Rust",
17911        FakeLspAdapter {
17912            name: language_server_name,
17913            initialization_options: Some(json!({
17914                "testOptionValue": true
17915            })),
17916            initializer: Some(Box::new(move |fake_server| {
17917                let task_restarts = Arc::clone(&closure_restarts);
17918                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17919                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17920                    futures::future::ready(Ok(()))
17921                });
17922            })),
17923            ..Default::default()
17924        },
17925    );
17926
17927    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17928    let _buffer = project
17929        .update(cx, |project, cx| {
17930            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17931        })
17932        .await
17933        .unwrap();
17934    let _fake_server = fake_servers.next().await.unwrap();
17935    update_test_language_settings(cx, |language_settings| {
17936        language_settings.languages.0.insert(
17937            language_name.clone().0,
17938            LanguageSettingsContent {
17939                tab_size: NonZeroU32::new(8),
17940                ..Default::default()
17941            },
17942        );
17943    });
17944    cx.executor().run_until_parked();
17945    assert_eq!(
17946        server_restarts.load(atomic::Ordering::Acquire),
17947        0,
17948        "Should not restart LSP server on an unrelated change"
17949    );
17950
17951    update_test_project_settings(cx, |project_settings| {
17952        project_settings.lsp.insert(
17953            "Some other server name".into(),
17954            LspSettings {
17955                binary: None,
17956                settings: None,
17957                initialization_options: Some(json!({
17958                    "some other init value": false
17959                })),
17960                enable_lsp_tasks: false,
17961                fetch: None,
17962            },
17963        );
17964    });
17965    cx.executor().run_until_parked();
17966    assert_eq!(
17967        server_restarts.load(atomic::Ordering::Acquire),
17968        0,
17969        "Should not restart LSP server on an unrelated LSP settings change"
17970    );
17971
17972    update_test_project_settings(cx, |project_settings| {
17973        project_settings.lsp.insert(
17974            language_server_name.into(),
17975            LspSettings {
17976                binary: None,
17977                settings: None,
17978                initialization_options: Some(json!({
17979                    "anotherInitValue": false
17980                })),
17981                enable_lsp_tasks: false,
17982                fetch: None,
17983            },
17984        );
17985    });
17986    cx.executor().run_until_parked();
17987    assert_eq!(
17988        server_restarts.load(atomic::Ordering::Acquire),
17989        1,
17990        "Should restart LSP server on a related LSP settings change"
17991    );
17992
17993    update_test_project_settings(cx, |project_settings| {
17994        project_settings.lsp.insert(
17995            language_server_name.into(),
17996            LspSettings {
17997                binary: None,
17998                settings: None,
17999                initialization_options: Some(json!({
18000                    "anotherInitValue": false
18001                })),
18002                enable_lsp_tasks: false,
18003                fetch: None,
18004            },
18005        );
18006    });
18007    cx.executor().run_until_parked();
18008    assert_eq!(
18009        server_restarts.load(atomic::Ordering::Acquire),
18010        1,
18011        "Should not restart LSP server on a related LSP settings change that is the same"
18012    );
18013
18014    update_test_project_settings(cx, |project_settings| {
18015        project_settings.lsp.insert(
18016            language_server_name.into(),
18017            LspSettings {
18018                binary: None,
18019                settings: None,
18020                initialization_options: None,
18021                enable_lsp_tasks: false,
18022                fetch: None,
18023            },
18024        );
18025    });
18026    cx.executor().run_until_parked();
18027    assert_eq!(
18028        server_restarts.load(atomic::Ordering::Acquire),
18029        2,
18030        "Should restart LSP server on another related LSP settings change"
18031    );
18032}
18033
18034#[gpui::test]
18035async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18036    init_test(cx, |_| {});
18037
18038    let mut cx = EditorLspTestContext::new_rust(
18039        lsp::ServerCapabilities {
18040            completion_provider: Some(lsp::CompletionOptions {
18041                trigger_characters: Some(vec![".".to_string()]),
18042                resolve_provider: Some(true),
18043                ..Default::default()
18044            }),
18045            ..Default::default()
18046        },
18047        cx,
18048    )
18049    .await;
18050
18051    cx.set_state("fn main() { let a = 2ˇ; }");
18052    cx.simulate_keystroke(".");
18053    let completion_item = lsp::CompletionItem {
18054        label: "some".into(),
18055        kind: Some(lsp::CompletionItemKind::SNIPPET),
18056        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18057        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18058            kind: lsp::MarkupKind::Markdown,
18059            value: "```rust\nSome(2)\n```".to_string(),
18060        })),
18061        deprecated: Some(false),
18062        sort_text: Some("fffffff2".to_string()),
18063        filter_text: Some("some".to_string()),
18064        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18065        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18066            range: lsp::Range {
18067                start: lsp::Position {
18068                    line: 0,
18069                    character: 22,
18070                },
18071                end: lsp::Position {
18072                    line: 0,
18073                    character: 22,
18074                },
18075            },
18076            new_text: "Some(2)".to_string(),
18077        })),
18078        additional_text_edits: Some(vec![lsp::TextEdit {
18079            range: lsp::Range {
18080                start: lsp::Position {
18081                    line: 0,
18082                    character: 20,
18083                },
18084                end: lsp::Position {
18085                    line: 0,
18086                    character: 22,
18087                },
18088            },
18089            new_text: "".to_string(),
18090        }]),
18091        ..Default::default()
18092    };
18093
18094    let closure_completion_item = completion_item.clone();
18095    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18096        let task_completion_item = closure_completion_item.clone();
18097        async move {
18098            Ok(Some(lsp::CompletionResponse::Array(vec![
18099                task_completion_item,
18100            ])))
18101        }
18102    });
18103
18104    request.next().await;
18105
18106    cx.condition(|editor, _| editor.context_menu_visible())
18107        .await;
18108    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18109        editor
18110            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18111            .unwrap()
18112    });
18113    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18114
18115    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18116        let task_completion_item = completion_item.clone();
18117        async move { Ok(task_completion_item) }
18118    })
18119    .next()
18120    .await
18121    .unwrap();
18122    apply_additional_edits.await.unwrap();
18123    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18124}
18125
18126#[gpui::test]
18127async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18128    init_test(cx, |_| {});
18129
18130    let mut cx = EditorLspTestContext::new_rust(
18131        lsp::ServerCapabilities {
18132            completion_provider: Some(lsp::CompletionOptions {
18133                trigger_characters: Some(vec![".".to_string()]),
18134                resolve_provider: Some(true),
18135                ..Default::default()
18136            }),
18137            ..Default::default()
18138        },
18139        cx,
18140    )
18141    .await;
18142
18143    cx.set_state("fn main() { let a = 2ˇ; }");
18144    cx.simulate_keystroke(".");
18145
18146    let item1 = lsp::CompletionItem {
18147        label: "method id()".to_string(),
18148        filter_text: Some("id".to_string()),
18149        detail: None,
18150        documentation: None,
18151        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18152            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18153            new_text: ".id".to_string(),
18154        })),
18155        ..lsp::CompletionItem::default()
18156    };
18157
18158    let item2 = lsp::CompletionItem {
18159        label: "other".to_string(),
18160        filter_text: Some("other".to_string()),
18161        detail: None,
18162        documentation: None,
18163        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18164            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18165            new_text: ".other".to_string(),
18166        })),
18167        ..lsp::CompletionItem::default()
18168    };
18169
18170    let item1 = item1.clone();
18171    cx.set_request_handler::<lsp::request::Completion, _, _>({
18172        let item1 = item1.clone();
18173        move |_, _, _| {
18174            let item1 = item1.clone();
18175            let item2 = item2.clone();
18176            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18177        }
18178    })
18179    .next()
18180    .await;
18181
18182    cx.condition(|editor, _| editor.context_menu_visible())
18183        .await;
18184    cx.update_editor(|editor, _, _| {
18185        let context_menu = editor.context_menu.borrow_mut();
18186        let context_menu = context_menu
18187            .as_ref()
18188            .expect("Should have the context menu deployed");
18189        match context_menu {
18190            CodeContextMenu::Completions(completions_menu) => {
18191                let completions = completions_menu.completions.borrow_mut();
18192                assert_eq!(
18193                    completions
18194                        .iter()
18195                        .map(|completion| &completion.label.text)
18196                        .collect::<Vec<_>>(),
18197                    vec!["method id()", "other"]
18198                )
18199            }
18200            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18201        }
18202    });
18203
18204    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18205        let item1 = item1.clone();
18206        move |_, item_to_resolve, _| {
18207            let item1 = item1.clone();
18208            async move {
18209                if item1 == item_to_resolve {
18210                    Ok(lsp::CompletionItem {
18211                        label: "method id()".to_string(),
18212                        filter_text: Some("id".to_string()),
18213                        detail: Some("Now resolved!".to_string()),
18214                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18215                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18216                            range: lsp::Range::new(
18217                                lsp::Position::new(0, 22),
18218                                lsp::Position::new(0, 22),
18219                            ),
18220                            new_text: ".id".to_string(),
18221                        })),
18222                        ..lsp::CompletionItem::default()
18223                    })
18224                } else {
18225                    Ok(item_to_resolve)
18226                }
18227            }
18228        }
18229    })
18230    .next()
18231    .await
18232    .unwrap();
18233    cx.run_until_parked();
18234
18235    cx.update_editor(|editor, window, cx| {
18236        editor.context_menu_next(&Default::default(), window, cx);
18237    });
18238
18239    cx.update_editor(|editor, _, _| {
18240        let context_menu = editor.context_menu.borrow_mut();
18241        let context_menu = context_menu
18242            .as_ref()
18243            .expect("Should have the context menu deployed");
18244        match context_menu {
18245            CodeContextMenu::Completions(completions_menu) => {
18246                let completions = completions_menu.completions.borrow_mut();
18247                assert_eq!(
18248                    completions
18249                        .iter()
18250                        .map(|completion| &completion.label.text)
18251                        .collect::<Vec<_>>(),
18252                    vec!["method id() Now resolved!", "other"],
18253                    "Should update first completion label, but not second as the filter text did not match."
18254                );
18255            }
18256            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18257        }
18258    });
18259}
18260
18261#[gpui::test]
18262async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18263    init_test(cx, |_| {});
18264    let mut cx = EditorLspTestContext::new_rust(
18265        lsp::ServerCapabilities {
18266            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18267            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18268            completion_provider: Some(lsp::CompletionOptions {
18269                resolve_provider: Some(true),
18270                ..Default::default()
18271            }),
18272            ..Default::default()
18273        },
18274        cx,
18275    )
18276    .await;
18277    cx.set_state(indoc! {"
18278        struct TestStruct {
18279            field: i32
18280        }
18281
18282        fn mainˇ() {
18283            let unused_var = 42;
18284            let test_struct = TestStruct { field: 42 };
18285        }
18286    "});
18287    let symbol_range = cx.lsp_range(indoc! {"
18288        struct TestStruct {
18289            field: i32
18290        }
18291
18292        «fn main»() {
18293            let unused_var = 42;
18294            let test_struct = TestStruct { field: 42 };
18295        }
18296    "});
18297    let mut hover_requests =
18298        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18299            Ok(Some(lsp::Hover {
18300                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18301                    kind: lsp::MarkupKind::Markdown,
18302                    value: "Function documentation".to_string(),
18303                }),
18304                range: Some(symbol_range),
18305            }))
18306        });
18307
18308    // Case 1: Test that code action menu hide hover popover
18309    cx.dispatch_action(Hover);
18310    hover_requests.next().await;
18311    cx.condition(|editor, _| editor.hover_state.visible()).await;
18312    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18313        move |_, _, _| async move {
18314            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18315                lsp::CodeAction {
18316                    title: "Remove unused variable".to_string(),
18317                    kind: Some(CodeActionKind::QUICKFIX),
18318                    edit: Some(lsp::WorkspaceEdit {
18319                        changes: Some(
18320                            [(
18321                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18322                                vec![lsp::TextEdit {
18323                                    range: lsp::Range::new(
18324                                        lsp::Position::new(5, 4),
18325                                        lsp::Position::new(5, 27),
18326                                    ),
18327                                    new_text: "".to_string(),
18328                                }],
18329                            )]
18330                            .into_iter()
18331                            .collect(),
18332                        ),
18333                        ..Default::default()
18334                    }),
18335                    ..Default::default()
18336                },
18337            )]))
18338        },
18339    );
18340    cx.update_editor(|editor, window, cx| {
18341        editor.toggle_code_actions(
18342            &ToggleCodeActions {
18343                deployed_from: None,
18344                quick_launch: false,
18345            },
18346            window,
18347            cx,
18348        );
18349    });
18350    code_action_requests.next().await;
18351    cx.run_until_parked();
18352    cx.condition(|editor, _| editor.context_menu_visible())
18353        .await;
18354    cx.update_editor(|editor, _, _| {
18355        assert!(
18356            !editor.hover_state.visible(),
18357            "Hover popover should be hidden when code action menu is shown"
18358        );
18359        // Hide code actions
18360        editor.context_menu.take();
18361    });
18362
18363    // Case 2: Test that code completions hide hover popover
18364    cx.dispatch_action(Hover);
18365    hover_requests.next().await;
18366    cx.condition(|editor, _| editor.hover_state.visible()).await;
18367    let counter = Arc::new(AtomicUsize::new(0));
18368    let mut completion_requests =
18369        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18370            let counter = counter.clone();
18371            async move {
18372                counter.fetch_add(1, atomic::Ordering::Release);
18373                Ok(Some(lsp::CompletionResponse::Array(vec![
18374                    lsp::CompletionItem {
18375                        label: "main".into(),
18376                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18377                        detail: Some("() -> ()".to_string()),
18378                        ..Default::default()
18379                    },
18380                    lsp::CompletionItem {
18381                        label: "TestStruct".into(),
18382                        kind: Some(lsp::CompletionItemKind::STRUCT),
18383                        detail: Some("struct TestStruct".to_string()),
18384                        ..Default::default()
18385                    },
18386                ])))
18387            }
18388        });
18389    cx.update_editor(|editor, window, cx| {
18390        editor.show_completions(&ShowCompletions, window, cx);
18391    });
18392    completion_requests.next().await;
18393    cx.condition(|editor, _| editor.context_menu_visible())
18394        .await;
18395    cx.update_editor(|editor, _, _| {
18396        assert!(
18397            !editor.hover_state.visible(),
18398            "Hover popover should be hidden when completion menu is shown"
18399        );
18400    });
18401}
18402
18403#[gpui::test]
18404async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18405    init_test(cx, |_| {});
18406
18407    let mut cx = EditorLspTestContext::new_rust(
18408        lsp::ServerCapabilities {
18409            completion_provider: Some(lsp::CompletionOptions {
18410                trigger_characters: Some(vec![".".to_string()]),
18411                resolve_provider: Some(true),
18412                ..Default::default()
18413            }),
18414            ..Default::default()
18415        },
18416        cx,
18417    )
18418    .await;
18419
18420    cx.set_state("fn main() { let a = 2ˇ; }");
18421    cx.simulate_keystroke(".");
18422
18423    let unresolved_item_1 = lsp::CompletionItem {
18424        label: "id".to_string(),
18425        filter_text: Some("id".to_string()),
18426        detail: None,
18427        documentation: None,
18428        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18429            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18430            new_text: ".id".to_string(),
18431        })),
18432        ..lsp::CompletionItem::default()
18433    };
18434    let resolved_item_1 = lsp::CompletionItem {
18435        additional_text_edits: Some(vec![lsp::TextEdit {
18436            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18437            new_text: "!!".to_string(),
18438        }]),
18439        ..unresolved_item_1.clone()
18440    };
18441    let unresolved_item_2 = lsp::CompletionItem {
18442        label: "other".to_string(),
18443        filter_text: Some("other".to_string()),
18444        detail: None,
18445        documentation: None,
18446        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18447            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18448            new_text: ".other".to_string(),
18449        })),
18450        ..lsp::CompletionItem::default()
18451    };
18452    let resolved_item_2 = lsp::CompletionItem {
18453        additional_text_edits: Some(vec![lsp::TextEdit {
18454            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18455            new_text: "??".to_string(),
18456        }]),
18457        ..unresolved_item_2.clone()
18458    };
18459
18460    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18461    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18462    cx.lsp
18463        .server
18464        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18465            let unresolved_item_1 = unresolved_item_1.clone();
18466            let resolved_item_1 = resolved_item_1.clone();
18467            let unresolved_item_2 = unresolved_item_2.clone();
18468            let resolved_item_2 = resolved_item_2.clone();
18469            let resolve_requests_1 = resolve_requests_1.clone();
18470            let resolve_requests_2 = resolve_requests_2.clone();
18471            move |unresolved_request, _| {
18472                let unresolved_item_1 = unresolved_item_1.clone();
18473                let resolved_item_1 = resolved_item_1.clone();
18474                let unresolved_item_2 = unresolved_item_2.clone();
18475                let resolved_item_2 = resolved_item_2.clone();
18476                let resolve_requests_1 = resolve_requests_1.clone();
18477                let resolve_requests_2 = resolve_requests_2.clone();
18478                async move {
18479                    if unresolved_request == unresolved_item_1 {
18480                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18481                        Ok(resolved_item_1.clone())
18482                    } else if unresolved_request == unresolved_item_2 {
18483                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18484                        Ok(resolved_item_2.clone())
18485                    } else {
18486                        panic!("Unexpected completion item {unresolved_request:?}")
18487                    }
18488                }
18489            }
18490        })
18491        .detach();
18492
18493    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18494        let unresolved_item_1 = unresolved_item_1.clone();
18495        let unresolved_item_2 = unresolved_item_2.clone();
18496        async move {
18497            Ok(Some(lsp::CompletionResponse::Array(vec![
18498                unresolved_item_1,
18499                unresolved_item_2,
18500            ])))
18501        }
18502    })
18503    .next()
18504    .await;
18505
18506    cx.condition(|editor, _| editor.context_menu_visible())
18507        .await;
18508    cx.update_editor(|editor, _, _| {
18509        let context_menu = editor.context_menu.borrow_mut();
18510        let context_menu = context_menu
18511            .as_ref()
18512            .expect("Should have the context menu deployed");
18513        match context_menu {
18514            CodeContextMenu::Completions(completions_menu) => {
18515                let completions = completions_menu.completions.borrow_mut();
18516                assert_eq!(
18517                    completions
18518                        .iter()
18519                        .map(|completion| &completion.label.text)
18520                        .collect::<Vec<_>>(),
18521                    vec!["id", "other"]
18522                )
18523            }
18524            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18525        }
18526    });
18527    cx.run_until_parked();
18528
18529    cx.update_editor(|editor, window, cx| {
18530        editor.context_menu_next(&ContextMenuNext, window, cx);
18531    });
18532    cx.run_until_parked();
18533    cx.update_editor(|editor, window, cx| {
18534        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18535    });
18536    cx.run_until_parked();
18537    cx.update_editor(|editor, window, cx| {
18538        editor.context_menu_next(&ContextMenuNext, window, cx);
18539    });
18540    cx.run_until_parked();
18541    cx.update_editor(|editor, window, cx| {
18542        editor
18543            .compose_completion(&ComposeCompletion::default(), window, cx)
18544            .expect("No task returned")
18545    })
18546    .await
18547    .expect("Completion failed");
18548    cx.run_until_parked();
18549
18550    cx.update_editor(|editor, _, cx| {
18551        assert_eq!(
18552            resolve_requests_1.load(atomic::Ordering::Acquire),
18553            1,
18554            "Should always resolve once despite multiple selections"
18555        );
18556        assert_eq!(
18557            resolve_requests_2.load(atomic::Ordering::Acquire),
18558            1,
18559            "Should always resolve once after multiple selections and applying the completion"
18560        );
18561        assert_eq!(
18562            editor.text(cx),
18563            "fn main() { let a = ??.other; }",
18564            "Should use resolved data when applying the completion"
18565        );
18566    });
18567}
18568
18569#[gpui::test]
18570async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18571    init_test(cx, |_| {});
18572
18573    let item_0 = lsp::CompletionItem {
18574        label: "abs".into(),
18575        insert_text: Some("abs".into()),
18576        data: Some(json!({ "very": "special"})),
18577        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18578        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18579            lsp::InsertReplaceEdit {
18580                new_text: "abs".to_string(),
18581                insert: lsp::Range::default(),
18582                replace: lsp::Range::default(),
18583            },
18584        )),
18585        ..lsp::CompletionItem::default()
18586    };
18587    let items = iter::once(item_0.clone())
18588        .chain((11..51).map(|i| lsp::CompletionItem {
18589            label: format!("item_{}", i),
18590            insert_text: Some(format!("item_{}", i)),
18591            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18592            ..lsp::CompletionItem::default()
18593        }))
18594        .collect::<Vec<_>>();
18595
18596    let default_commit_characters = vec!["?".to_string()];
18597    let default_data = json!({ "default": "data"});
18598    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18599    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18600    let default_edit_range = lsp::Range {
18601        start: lsp::Position {
18602            line: 0,
18603            character: 5,
18604        },
18605        end: lsp::Position {
18606            line: 0,
18607            character: 5,
18608        },
18609    };
18610
18611    let mut cx = EditorLspTestContext::new_rust(
18612        lsp::ServerCapabilities {
18613            completion_provider: Some(lsp::CompletionOptions {
18614                trigger_characters: Some(vec![".".to_string()]),
18615                resolve_provider: Some(true),
18616                ..Default::default()
18617            }),
18618            ..Default::default()
18619        },
18620        cx,
18621    )
18622    .await;
18623
18624    cx.set_state("fn main() { let a = 2ˇ; }");
18625    cx.simulate_keystroke(".");
18626
18627    let completion_data = default_data.clone();
18628    let completion_characters = default_commit_characters.clone();
18629    let completion_items = items.clone();
18630    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18631        let default_data = completion_data.clone();
18632        let default_commit_characters = completion_characters.clone();
18633        let items = completion_items.clone();
18634        async move {
18635            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18636                items,
18637                item_defaults: Some(lsp::CompletionListItemDefaults {
18638                    data: Some(default_data.clone()),
18639                    commit_characters: Some(default_commit_characters.clone()),
18640                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18641                        default_edit_range,
18642                    )),
18643                    insert_text_format: Some(default_insert_text_format),
18644                    insert_text_mode: Some(default_insert_text_mode),
18645                }),
18646                ..lsp::CompletionList::default()
18647            })))
18648        }
18649    })
18650    .next()
18651    .await;
18652
18653    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18654    cx.lsp
18655        .server
18656        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18657            let closure_resolved_items = resolved_items.clone();
18658            move |item_to_resolve, _| {
18659                let closure_resolved_items = closure_resolved_items.clone();
18660                async move {
18661                    closure_resolved_items.lock().push(item_to_resolve.clone());
18662                    Ok(item_to_resolve)
18663                }
18664            }
18665        })
18666        .detach();
18667
18668    cx.condition(|editor, _| editor.context_menu_visible())
18669        .await;
18670    cx.run_until_parked();
18671    cx.update_editor(|editor, _, _| {
18672        let menu = editor.context_menu.borrow_mut();
18673        match menu.as_ref().expect("should have the completions menu") {
18674            CodeContextMenu::Completions(completions_menu) => {
18675                assert_eq!(
18676                    completions_menu
18677                        .entries
18678                        .borrow()
18679                        .iter()
18680                        .map(|mat| mat.string.clone())
18681                        .collect::<Vec<String>>(),
18682                    items
18683                        .iter()
18684                        .map(|completion| completion.label.clone())
18685                        .collect::<Vec<String>>()
18686                );
18687            }
18688            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18689        }
18690    });
18691    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18692    // with 4 from the end.
18693    assert_eq!(
18694        *resolved_items.lock(),
18695        [&items[0..16], &items[items.len() - 4..items.len()]]
18696            .concat()
18697            .iter()
18698            .cloned()
18699            .map(|mut item| {
18700                if item.data.is_none() {
18701                    item.data = Some(default_data.clone());
18702                }
18703                item
18704            })
18705            .collect::<Vec<lsp::CompletionItem>>(),
18706        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18707    );
18708    resolved_items.lock().clear();
18709
18710    cx.update_editor(|editor, window, cx| {
18711        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18712    });
18713    cx.run_until_parked();
18714    // Completions that have already been resolved are skipped.
18715    assert_eq!(
18716        *resolved_items.lock(),
18717        items[items.len() - 17..items.len() - 4]
18718            .iter()
18719            .cloned()
18720            .map(|mut item| {
18721                if item.data.is_none() {
18722                    item.data = Some(default_data.clone());
18723                }
18724                item
18725            })
18726            .collect::<Vec<lsp::CompletionItem>>()
18727    );
18728    resolved_items.lock().clear();
18729}
18730
18731#[gpui::test]
18732async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18733    init_test(cx, |_| {});
18734
18735    let mut cx = EditorLspTestContext::new(
18736        Language::new(
18737            LanguageConfig {
18738                matcher: LanguageMatcher {
18739                    path_suffixes: vec!["jsx".into()],
18740                    ..Default::default()
18741                },
18742                overrides: [(
18743                    "element".into(),
18744                    LanguageConfigOverride {
18745                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18746                        ..Default::default()
18747                    },
18748                )]
18749                .into_iter()
18750                .collect(),
18751                ..Default::default()
18752            },
18753            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18754        )
18755        .with_override_query("(jsx_self_closing_element) @element")
18756        .unwrap(),
18757        lsp::ServerCapabilities {
18758            completion_provider: Some(lsp::CompletionOptions {
18759                trigger_characters: Some(vec![":".to_string()]),
18760                ..Default::default()
18761            }),
18762            ..Default::default()
18763        },
18764        cx,
18765    )
18766    .await;
18767
18768    cx.lsp
18769        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18770            Ok(Some(lsp::CompletionResponse::Array(vec![
18771                lsp::CompletionItem {
18772                    label: "bg-blue".into(),
18773                    ..Default::default()
18774                },
18775                lsp::CompletionItem {
18776                    label: "bg-red".into(),
18777                    ..Default::default()
18778                },
18779                lsp::CompletionItem {
18780                    label: "bg-yellow".into(),
18781                    ..Default::default()
18782                },
18783            ])))
18784        });
18785
18786    cx.set_state(r#"<p class="bgˇ" />"#);
18787
18788    // Trigger completion when typing a dash, because the dash is an extra
18789    // word character in the 'element' scope, which contains the cursor.
18790    cx.simulate_keystroke("-");
18791    cx.executor().run_until_parked();
18792    cx.update_editor(|editor, _, _| {
18793        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18794        {
18795            assert_eq!(
18796                completion_menu_entries(menu),
18797                &["bg-blue", "bg-red", "bg-yellow"]
18798            );
18799        } else {
18800            panic!("expected completion menu to be open");
18801        }
18802    });
18803
18804    cx.simulate_keystroke("l");
18805    cx.executor().run_until_parked();
18806    cx.update_editor(|editor, _, _| {
18807        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18808        {
18809            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18810        } else {
18811            panic!("expected completion menu to be open");
18812        }
18813    });
18814
18815    // When filtering completions, consider the character after the '-' to
18816    // be the start of a subword.
18817    cx.set_state(r#"<p class="yelˇ" />"#);
18818    cx.simulate_keystroke("l");
18819    cx.executor().run_until_parked();
18820    cx.update_editor(|editor, _, _| {
18821        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18822        {
18823            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18824        } else {
18825            panic!("expected completion menu to be open");
18826        }
18827    });
18828}
18829
18830fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18831    let entries = menu.entries.borrow();
18832    entries.iter().map(|mat| mat.string.clone()).collect()
18833}
18834
18835#[gpui::test]
18836async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18837    init_test(cx, |settings| {
18838        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18839    });
18840
18841    let fs = FakeFs::new(cx.executor());
18842    fs.insert_file(path!("/file.ts"), Default::default()).await;
18843
18844    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18845    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18846
18847    language_registry.add(Arc::new(Language::new(
18848        LanguageConfig {
18849            name: "TypeScript".into(),
18850            matcher: LanguageMatcher {
18851                path_suffixes: vec!["ts".to_string()],
18852                ..Default::default()
18853            },
18854            ..Default::default()
18855        },
18856        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18857    )));
18858    update_test_language_settings(cx, |settings| {
18859        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18860    });
18861
18862    let test_plugin = "test_plugin";
18863    let _ = language_registry.register_fake_lsp(
18864        "TypeScript",
18865        FakeLspAdapter {
18866            prettier_plugins: vec![test_plugin],
18867            ..Default::default()
18868        },
18869    );
18870
18871    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18872    let buffer = project
18873        .update(cx, |project, cx| {
18874            project.open_local_buffer(path!("/file.ts"), cx)
18875        })
18876        .await
18877        .unwrap();
18878
18879    let buffer_text = "one\ntwo\nthree\n";
18880    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18881    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18882    editor.update_in(cx, |editor, window, cx| {
18883        editor.set_text(buffer_text, window, cx)
18884    });
18885
18886    editor
18887        .update_in(cx, |editor, window, cx| {
18888            editor.perform_format(
18889                project.clone(),
18890                FormatTrigger::Manual,
18891                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18892                window,
18893                cx,
18894            )
18895        })
18896        .unwrap()
18897        .await;
18898    assert_eq!(
18899        editor.update(cx, |editor, cx| editor.text(cx)),
18900        buffer_text.to_string() + prettier_format_suffix,
18901        "Test prettier formatting was not applied to the original buffer text",
18902    );
18903
18904    update_test_language_settings(cx, |settings| {
18905        settings.defaults.formatter = Some(FormatterList::default())
18906    });
18907    let format = editor.update_in(cx, |editor, window, cx| {
18908        editor.perform_format(
18909            project.clone(),
18910            FormatTrigger::Manual,
18911            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18912            window,
18913            cx,
18914        )
18915    });
18916    format.await.unwrap();
18917    assert_eq!(
18918        editor.update(cx, |editor, cx| editor.text(cx)),
18919        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18920        "Autoformatting (via test prettier) was not applied to the original buffer text",
18921    );
18922}
18923
18924#[gpui::test]
18925async fn test_addition_reverts(cx: &mut TestAppContext) {
18926    init_test(cx, |_| {});
18927    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18928    let base_text = indoc! {r#"
18929        struct Row;
18930        struct Row1;
18931        struct Row2;
18932
18933        struct Row4;
18934        struct Row5;
18935        struct Row6;
18936
18937        struct Row8;
18938        struct Row9;
18939        struct Row10;"#};
18940
18941    // When addition hunks are not adjacent to carets, no hunk revert is performed
18942    assert_hunk_revert(
18943        indoc! {r#"struct Row;
18944                   struct Row1;
18945                   struct Row1.1;
18946                   struct Row1.2;
18947                   struct Row2;ˇ
18948
18949                   struct Row4;
18950                   struct Row5;
18951                   struct Row6;
18952
18953                   struct Row8;
18954                   ˇstruct Row9;
18955                   struct Row9.1;
18956                   struct Row9.2;
18957                   struct Row9.3;
18958                   struct Row10;"#},
18959        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18960        indoc! {r#"struct Row;
18961                   struct Row1;
18962                   struct Row1.1;
18963                   struct Row1.2;
18964                   struct Row2;ˇ
18965
18966                   struct Row4;
18967                   struct Row5;
18968                   struct Row6;
18969
18970                   struct Row8;
18971                   ˇstruct Row9;
18972                   struct Row9.1;
18973                   struct Row9.2;
18974                   struct Row9.3;
18975                   struct Row10;"#},
18976        base_text,
18977        &mut cx,
18978    );
18979    // Same for selections
18980    assert_hunk_revert(
18981        indoc! {r#"struct Row;
18982                   struct Row1;
18983                   struct Row2;
18984                   struct Row2.1;
18985                   struct Row2.2;
18986                   «ˇ
18987                   struct Row4;
18988                   struct» Row5;
18989                   «struct Row6;
18990                   ˇ»
18991                   struct Row9.1;
18992                   struct Row9.2;
18993                   struct Row9.3;
18994                   struct Row8;
18995                   struct Row9;
18996                   struct Row10;"#},
18997        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18998        indoc! {r#"struct Row;
18999                   struct Row1;
19000                   struct Row2;
19001                   struct Row2.1;
19002                   struct Row2.2;
19003                   «ˇ
19004                   struct Row4;
19005                   struct» Row5;
19006                   «struct Row6;
19007                   ˇ»
19008                   struct Row9.1;
19009                   struct Row9.2;
19010                   struct Row9.3;
19011                   struct Row8;
19012                   struct Row9;
19013                   struct Row10;"#},
19014        base_text,
19015        &mut cx,
19016    );
19017
19018    // When carets and selections intersect the addition hunks, those are reverted.
19019    // Adjacent carets got merged.
19020    assert_hunk_revert(
19021        indoc! {r#"struct Row;
19022                   ˇ// something on the top
19023                   struct Row1;
19024                   struct Row2;
19025                   struct Roˇw3.1;
19026                   struct Row2.2;
19027                   struct Row2.3;ˇ
19028
19029                   struct Row4;
19030                   struct ˇRow5.1;
19031                   struct Row5.2;
19032                   struct «Rowˇ»5.3;
19033                   struct Row5;
19034                   struct Row6;
19035                   ˇ
19036                   struct Row9.1;
19037                   struct «Rowˇ»9.2;
19038                   struct «ˇRow»9.3;
19039                   struct Row8;
19040                   struct Row9;
19041                   «ˇ// something on bottom»
19042                   struct Row10;"#},
19043        vec![
19044            DiffHunkStatusKind::Added,
19045            DiffHunkStatusKind::Added,
19046            DiffHunkStatusKind::Added,
19047            DiffHunkStatusKind::Added,
19048            DiffHunkStatusKind::Added,
19049        ],
19050        indoc! {r#"struct Row;
19051                   ˇstruct Row1;
19052                   struct Row2;
19053                   ˇ
19054                   struct Row4;
19055                   ˇstruct Row5;
19056                   struct Row6;
19057                   ˇ
19058                   ˇstruct Row8;
19059                   struct Row9;
19060                   ˇstruct Row10;"#},
19061        base_text,
19062        &mut cx,
19063    );
19064}
19065
19066#[gpui::test]
19067async fn test_modification_reverts(cx: &mut TestAppContext) {
19068    init_test(cx, |_| {});
19069    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19070    let base_text = indoc! {r#"
19071        struct Row;
19072        struct Row1;
19073        struct Row2;
19074
19075        struct Row4;
19076        struct Row5;
19077        struct Row6;
19078
19079        struct Row8;
19080        struct Row9;
19081        struct Row10;"#};
19082
19083    // Modification hunks behave the same as the addition ones.
19084    assert_hunk_revert(
19085        indoc! {r#"struct Row;
19086                   struct Row1;
19087                   struct Row33;
19088                   ˇ
19089                   struct Row4;
19090                   struct Row5;
19091                   struct Row6;
19092                   ˇ
19093                   struct Row99;
19094                   struct Row9;
19095                   struct Row10;"#},
19096        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19097        indoc! {r#"struct Row;
19098                   struct Row1;
19099                   struct Row33;
19100                   ˇ
19101                   struct Row4;
19102                   struct Row5;
19103                   struct Row6;
19104                   ˇ
19105                   struct Row99;
19106                   struct Row9;
19107                   struct Row10;"#},
19108        base_text,
19109        &mut cx,
19110    );
19111    assert_hunk_revert(
19112        indoc! {r#"struct Row;
19113                   struct Row1;
19114                   struct Row33;
19115                   «ˇ
19116                   struct Row4;
19117                   struct» Row5;
19118                   «struct Row6;
19119                   ˇ»
19120                   struct Row99;
19121                   struct Row9;
19122                   struct Row10;"#},
19123        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19124        indoc! {r#"struct Row;
19125                   struct Row1;
19126                   struct Row33;
19127                   «ˇ
19128                   struct Row4;
19129                   struct» Row5;
19130                   «struct Row6;
19131                   ˇ»
19132                   struct Row99;
19133                   struct Row9;
19134                   struct Row10;"#},
19135        base_text,
19136        &mut cx,
19137    );
19138
19139    assert_hunk_revert(
19140        indoc! {r#"ˇstruct Row1.1;
19141                   struct Row1;
19142                   «ˇstr»uct Row22;
19143
19144                   struct ˇRow44;
19145                   struct Row5;
19146                   struct «Rˇ»ow66;ˇ
19147
19148                   «struˇ»ct Row88;
19149                   struct Row9;
19150                   struct Row1011;ˇ"#},
19151        vec![
19152            DiffHunkStatusKind::Modified,
19153            DiffHunkStatusKind::Modified,
19154            DiffHunkStatusKind::Modified,
19155            DiffHunkStatusKind::Modified,
19156            DiffHunkStatusKind::Modified,
19157            DiffHunkStatusKind::Modified,
19158        ],
19159        indoc! {r#"struct Row;
19160                   ˇstruct Row1;
19161                   struct Row2;
19162                   ˇ
19163                   struct Row4;
19164                   ˇstruct Row5;
19165                   struct Row6;
19166                   ˇ
19167                   struct Row8;
19168                   ˇstruct Row9;
19169                   struct Row10;ˇ"#},
19170        base_text,
19171        &mut cx,
19172    );
19173}
19174
19175#[gpui::test]
19176async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19177    init_test(cx, |_| {});
19178    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19179    let base_text = indoc! {r#"
19180        one
19181
19182        two
19183        three
19184        "#};
19185
19186    cx.set_head_text(base_text);
19187    cx.set_state("\nˇ\n");
19188    cx.executor().run_until_parked();
19189    cx.update_editor(|editor, _window, cx| {
19190        editor.expand_selected_diff_hunks(cx);
19191    });
19192    cx.executor().run_until_parked();
19193    cx.update_editor(|editor, window, cx| {
19194        editor.backspace(&Default::default(), window, cx);
19195    });
19196    cx.run_until_parked();
19197    cx.assert_state_with_diff(
19198        indoc! {r#"
19199
19200        - two
19201        - threeˇ
19202        +
19203        "#}
19204        .to_string(),
19205    );
19206}
19207
19208#[gpui::test]
19209async fn test_deletion_reverts(cx: &mut TestAppContext) {
19210    init_test(cx, |_| {});
19211    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19212    let base_text = indoc! {r#"struct Row;
19213struct Row1;
19214struct Row2;
19215
19216struct Row4;
19217struct Row5;
19218struct Row6;
19219
19220struct Row8;
19221struct Row9;
19222struct Row10;"#};
19223
19224    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19225    assert_hunk_revert(
19226        indoc! {r#"struct Row;
19227                   struct Row2;
19228
19229                   ˇstruct Row4;
19230                   struct Row5;
19231                   struct Row6;
19232                   ˇ
19233                   struct Row8;
19234                   struct Row10;"#},
19235        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19236        indoc! {r#"struct Row;
19237                   struct Row2;
19238
19239                   ˇstruct Row4;
19240                   struct Row5;
19241                   struct Row6;
19242                   ˇ
19243                   struct Row8;
19244                   struct Row10;"#},
19245        base_text,
19246        &mut cx,
19247    );
19248    assert_hunk_revert(
19249        indoc! {r#"struct Row;
19250                   struct Row2;
19251
19252                   «ˇstruct Row4;
19253                   struct» Row5;
19254                   «struct Row6;
19255                   ˇ»
19256                   struct Row8;
19257                   struct Row10;"#},
19258        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19259        indoc! {r#"struct Row;
19260                   struct Row2;
19261
19262                   «ˇstruct Row4;
19263                   struct» Row5;
19264                   «struct Row6;
19265                   ˇ»
19266                   struct Row8;
19267                   struct Row10;"#},
19268        base_text,
19269        &mut cx,
19270    );
19271
19272    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19273    assert_hunk_revert(
19274        indoc! {r#"struct Row;
19275                   ˇstruct Row2;
19276
19277                   struct Row4;
19278                   struct Row5;
19279                   struct Row6;
19280
19281                   struct Row8;ˇ
19282                   struct Row10;"#},
19283        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19284        indoc! {r#"struct Row;
19285                   struct Row1;
19286                   ˇstruct Row2;
19287
19288                   struct Row4;
19289                   struct Row5;
19290                   struct Row6;
19291
19292                   struct Row8;ˇ
19293                   struct Row9;
19294                   struct Row10;"#},
19295        base_text,
19296        &mut cx,
19297    );
19298    assert_hunk_revert(
19299        indoc! {r#"struct Row;
19300                   struct Row2«ˇ;
19301                   struct Row4;
19302                   struct» Row5;
19303                   «struct Row6;
19304
19305                   struct Row8;ˇ»
19306                   struct Row10;"#},
19307        vec![
19308            DiffHunkStatusKind::Deleted,
19309            DiffHunkStatusKind::Deleted,
19310            DiffHunkStatusKind::Deleted,
19311        ],
19312        indoc! {r#"struct Row;
19313                   struct Row1;
19314                   struct Row2«ˇ;
19315
19316                   struct Row4;
19317                   struct» Row5;
19318                   «struct Row6;
19319
19320                   struct Row8;ˇ»
19321                   struct Row9;
19322                   struct Row10;"#},
19323        base_text,
19324        &mut cx,
19325    );
19326}
19327
19328#[gpui::test]
19329async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19330    init_test(cx, |_| {});
19331
19332    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19333    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19334    let base_text_3 =
19335        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19336
19337    let text_1 = edit_first_char_of_every_line(base_text_1);
19338    let text_2 = edit_first_char_of_every_line(base_text_2);
19339    let text_3 = edit_first_char_of_every_line(base_text_3);
19340
19341    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19342    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19343    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19344
19345    let multibuffer = cx.new(|cx| {
19346        let mut multibuffer = MultiBuffer::new(ReadWrite);
19347        multibuffer.push_excerpts(
19348            buffer_1.clone(),
19349            [
19350                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19351                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19352                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19353            ],
19354            cx,
19355        );
19356        multibuffer.push_excerpts(
19357            buffer_2.clone(),
19358            [
19359                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19360                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19361                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19362            ],
19363            cx,
19364        );
19365        multibuffer.push_excerpts(
19366            buffer_3.clone(),
19367            [
19368                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19369                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19370                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19371            ],
19372            cx,
19373        );
19374        multibuffer
19375    });
19376
19377    let fs = FakeFs::new(cx.executor());
19378    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19379    let (editor, cx) = cx
19380        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19381    editor.update_in(cx, |editor, _window, cx| {
19382        for (buffer, diff_base) in [
19383            (buffer_1.clone(), base_text_1),
19384            (buffer_2.clone(), base_text_2),
19385            (buffer_3.clone(), base_text_3),
19386        ] {
19387            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19388            editor
19389                .buffer
19390                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19391        }
19392    });
19393    cx.executor().run_until_parked();
19394
19395    editor.update_in(cx, |editor, window, cx| {
19396        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}");
19397        editor.select_all(&SelectAll, window, cx);
19398        editor.git_restore(&Default::default(), window, cx);
19399    });
19400    cx.executor().run_until_parked();
19401
19402    // When all ranges are selected, all buffer hunks are reverted.
19403    editor.update(cx, |editor, cx| {
19404        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");
19405    });
19406    buffer_1.update(cx, |buffer, _| {
19407        assert_eq!(buffer.text(), base_text_1);
19408    });
19409    buffer_2.update(cx, |buffer, _| {
19410        assert_eq!(buffer.text(), base_text_2);
19411    });
19412    buffer_3.update(cx, |buffer, _| {
19413        assert_eq!(buffer.text(), base_text_3);
19414    });
19415
19416    editor.update_in(cx, |editor, window, cx| {
19417        editor.undo(&Default::default(), window, cx);
19418    });
19419
19420    editor.update_in(cx, |editor, window, cx| {
19421        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19422            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19423        });
19424        editor.git_restore(&Default::default(), window, cx);
19425    });
19426
19427    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19428    // but not affect buffer_2 and its related excerpts.
19429    editor.update(cx, |editor, cx| {
19430        assert_eq!(
19431            editor.text(cx),
19432            "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}"
19433        );
19434    });
19435    buffer_1.update(cx, |buffer, _| {
19436        assert_eq!(buffer.text(), base_text_1);
19437    });
19438    buffer_2.update(cx, |buffer, _| {
19439        assert_eq!(
19440            buffer.text(),
19441            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19442        );
19443    });
19444    buffer_3.update(cx, |buffer, _| {
19445        assert_eq!(
19446            buffer.text(),
19447            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19448        );
19449    });
19450
19451    fn edit_first_char_of_every_line(text: &str) -> String {
19452        text.split('\n')
19453            .map(|line| format!("X{}", &line[1..]))
19454            .collect::<Vec<_>>()
19455            .join("\n")
19456    }
19457}
19458
19459#[gpui::test]
19460async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19461    init_test(cx, |_| {});
19462
19463    let cols = 4;
19464    let rows = 10;
19465    let sample_text_1 = sample_text(rows, cols, 'a');
19466    assert_eq!(
19467        sample_text_1,
19468        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19469    );
19470    let sample_text_2 = sample_text(rows, cols, 'l');
19471    assert_eq!(
19472        sample_text_2,
19473        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19474    );
19475    let sample_text_3 = sample_text(rows, cols, 'v');
19476    assert_eq!(
19477        sample_text_3,
19478        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19479    );
19480
19481    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19482    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19483    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19484
19485    let multi_buffer = cx.new(|cx| {
19486        let mut multibuffer = MultiBuffer::new(ReadWrite);
19487        multibuffer.push_excerpts(
19488            buffer_1.clone(),
19489            [
19490                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19491                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19492                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19493            ],
19494            cx,
19495        );
19496        multibuffer.push_excerpts(
19497            buffer_2.clone(),
19498            [
19499                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19500                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19501                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19502            ],
19503            cx,
19504        );
19505        multibuffer.push_excerpts(
19506            buffer_3.clone(),
19507            [
19508                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19509                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19510                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19511            ],
19512            cx,
19513        );
19514        multibuffer
19515    });
19516
19517    let fs = FakeFs::new(cx.executor());
19518    fs.insert_tree(
19519        "/a",
19520        json!({
19521            "main.rs": sample_text_1,
19522            "other.rs": sample_text_2,
19523            "lib.rs": sample_text_3,
19524        }),
19525    )
19526    .await;
19527    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19528    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19529    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19530    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19531        Editor::new(
19532            EditorMode::full(),
19533            multi_buffer,
19534            Some(project.clone()),
19535            window,
19536            cx,
19537        )
19538    });
19539    let multibuffer_item_id = workspace
19540        .update(cx, |workspace, window, cx| {
19541            assert!(
19542                workspace.active_item(cx).is_none(),
19543                "active item should be None before the first item is added"
19544            );
19545            workspace.add_item_to_active_pane(
19546                Box::new(multi_buffer_editor.clone()),
19547                None,
19548                true,
19549                window,
19550                cx,
19551            );
19552            let active_item = workspace
19553                .active_item(cx)
19554                .expect("should have an active item after adding the multi buffer");
19555            assert_eq!(
19556                active_item.buffer_kind(cx),
19557                ItemBufferKind::Multibuffer,
19558                "A multi buffer was expected to active after adding"
19559            );
19560            active_item.item_id()
19561        })
19562        .unwrap();
19563    cx.executor().run_until_parked();
19564
19565    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19566        editor.change_selections(
19567            SelectionEffects::scroll(Autoscroll::Next),
19568            window,
19569            cx,
19570            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
19571        );
19572        editor.open_excerpts(&OpenExcerpts, window, cx);
19573    });
19574    cx.executor().run_until_parked();
19575    let first_item_id = workspace
19576        .update(cx, |workspace, window, cx| {
19577            let active_item = workspace
19578                .active_item(cx)
19579                .expect("should have an active item after navigating into the 1st buffer");
19580            let first_item_id = active_item.item_id();
19581            assert_ne!(
19582                first_item_id, multibuffer_item_id,
19583                "Should navigate into the 1st buffer and activate it"
19584            );
19585            assert_eq!(
19586                active_item.buffer_kind(cx),
19587                ItemBufferKind::Singleton,
19588                "New active item should be a singleton buffer"
19589            );
19590            assert_eq!(
19591                active_item
19592                    .act_as::<Editor>(cx)
19593                    .expect("should have navigated into an editor for the 1st buffer")
19594                    .read(cx)
19595                    .text(cx),
19596                sample_text_1
19597            );
19598
19599            workspace
19600                .go_back(workspace.active_pane().downgrade(), window, cx)
19601                .detach_and_log_err(cx);
19602
19603            first_item_id
19604        })
19605        .unwrap();
19606    cx.executor().run_until_parked();
19607    workspace
19608        .update(cx, |workspace, _, cx| {
19609            let active_item = workspace
19610                .active_item(cx)
19611                .expect("should have an active item after navigating back");
19612            assert_eq!(
19613                active_item.item_id(),
19614                multibuffer_item_id,
19615                "Should navigate back to the multi buffer"
19616            );
19617            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19618        })
19619        .unwrap();
19620
19621    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19622        editor.change_selections(
19623            SelectionEffects::scroll(Autoscroll::Next),
19624            window,
19625            cx,
19626            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
19627        );
19628        editor.open_excerpts(&OpenExcerpts, window, cx);
19629    });
19630    cx.executor().run_until_parked();
19631    let second_item_id = workspace
19632        .update(cx, |workspace, window, cx| {
19633            let active_item = workspace
19634                .active_item(cx)
19635                .expect("should have an active item after navigating into the 2nd buffer");
19636            let second_item_id = active_item.item_id();
19637            assert_ne!(
19638                second_item_id, multibuffer_item_id,
19639                "Should navigate away from the multibuffer"
19640            );
19641            assert_ne!(
19642                second_item_id, first_item_id,
19643                "Should navigate into the 2nd buffer and activate it"
19644            );
19645            assert_eq!(
19646                active_item.buffer_kind(cx),
19647                ItemBufferKind::Singleton,
19648                "New active item should be a singleton buffer"
19649            );
19650            assert_eq!(
19651                active_item
19652                    .act_as::<Editor>(cx)
19653                    .expect("should have navigated into an editor")
19654                    .read(cx)
19655                    .text(cx),
19656                sample_text_2
19657            );
19658
19659            workspace
19660                .go_back(workspace.active_pane().downgrade(), window, cx)
19661                .detach_and_log_err(cx);
19662
19663            second_item_id
19664        })
19665        .unwrap();
19666    cx.executor().run_until_parked();
19667    workspace
19668        .update(cx, |workspace, _, cx| {
19669            let active_item = workspace
19670                .active_item(cx)
19671                .expect("should have an active item after navigating back from the 2nd buffer");
19672            assert_eq!(
19673                active_item.item_id(),
19674                multibuffer_item_id,
19675                "Should navigate back from the 2nd buffer to the multi buffer"
19676            );
19677            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19678        })
19679        .unwrap();
19680
19681    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19682        editor.change_selections(
19683            SelectionEffects::scroll(Autoscroll::Next),
19684            window,
19685            cx,
19686            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
19687        );
19688        editor.open_excerpts(&OpenExcerpts, window, cx);
19689    });
19690    cx.executor().run_until_parked();
19691    workspace
19692        .update(cx, |workspace, window, cx| {
19693            let active_item = workspace
19694                .active_item(cx)
19695                .expect("should have an active item after navigating into the 3rd buffer");
19696            let third_item_id = active_item.item_id();
19697            assert_ne!(
19698                third_item_id, multibuffer_item_id,
19699                "Should navigate into the 3rd buffer and activate it"
19700            );
19701            assert_ne!(third_item_id, first_item_id);
19702            assert_ne!(third_item_id, second_item_id);
19703            assert_eq!(
19704                active_item.buffer_kind(cx),
19705                ItemBufferKind::Singleton,
19706                "New active item should be a singleton buffer"
19707            );
19708            assert_eq!(
19709                active_item
19710                    .act_as::<Editor>(cx)
19711                    .expect("should have navigated into an editor")
19712                    .read(cx)
19713                    .text(cx),
19714                sample_text_3
19715            );
19716
19717            workspace
19718                .go_back(workspace.active_pane().downgrade(), window, cx)
19719                .detach_and_log_err(cx);
19720        })
19721        .unwrap();
19722    cx.executor().run_until_parked();
19723    workspace
19724        .update(cx, |workspace, _, cx| {
19725            let active_item = workspace
19726                .active_item(cx)
19727                .expect("should have an active item after navigating back from the 3rd buffer");
19728            assert_eq!(
19729                active_item.item_id(),
19730                multibuffer_item_id,
19731                "Should navigate back from the 3rd buffer to the multi buffer"
19732            );
19733            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19734        })
19735        .unwrap();
19736}
19737
19738#[gpui::test]
19739async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19740    init_test(cx, |_| {});
19741
19742    let mut cx = EditorTestContext::new(cx).await;
19743
19744    let diff_base = r#"
19745        use some::mod;
19746
19747        const A: u32 = 42;
19748
19749        fn main() {
19750            println!("hello");
19751
19752            println!("world");
19753        }
19754        "#
19755    .unindent();
19756
19757    cx.set_state(
19758        &r#"
19759        use some::modified;
19760
19761        ˇ
19762        fn main() {
19763            println!("hello there");
19764
19765            println!("around the");
19766            println!("world");
19767        }
19768        "#
19769        .unindent(),
19770    );
19771
19772    cx.set_head_text(&diff_base);
19773    executor.run_until_parked();
19774
19775    cx.update_editor(|editor, window, cx| {
19776        editor.go_to_next_hunk(&GoToHunk, window, cx);
19777        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19778    });
19779    executor.run_until_parked();
19780    cx.assert_state_with_diff(
19781        r#"
19782          use some::modified;
19783
19784
19785          fn main() {
19786        -     println!("hello");
19787        + ˇ    println!("hello there");
19788
19789              println!("around the");
19790              println!("world");
19791          }
19792        "#
19793        .unindent(),
19794    );
19795
19796    cx.update_editor(|editor, window, cx| {
19797        for _ in 0..2 {
19798            editor.go_to_next_hunk(&GoToHunk, window, cx);
19799            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19800        }
19801    });
19802    executor.run_until_parked();
19803    cx.assert_state_with_diff(
19804        r#"
19805        - use some::mod;
19806        + ˇuse some::modified;
19807
19808
19809          fn main() {
19810        -     println!("hello");
19811        +     println!("hello there");
19812
19813        +     println!("around the");
19814              println!("world");
19815          }
19816        "#
19817        .unindent(),
19818    );
19819
19820    cx.update_editor(|editor, window, cx| {
19821        editor.go_to_next_hunk(&GoToHunk, window, cx);
19822        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19823    });
19824    executor.run_until_parked();
19825    cx.assert_state_with_diff(
19826        r#"
19827        - use some::mod;
19828        + use some::modified;
19829
19830        - const A: u32 = 42;
19831          ˇ
19832          fn main() {
19833        -     println!("hello");
19834        +     println!("hello there");
19835
19836        +     println!("around the");
19837              println!("world");
19838          }
19839        "#
19840        .unindent(),
19841    );
19842
19843    cx.update_editor(|editor, window, cx| {
19844        editor.cancel(&Cancel, window, cx);
19845    });
19846
19847    cx.assert_state_with_diff(
19848        r#"
19849          use some::modified;
19850
19851          ˇ
19852          fn main() {
19853              println!("hello there");
19854
19855              println!("around the");
19856              println!("world");
19857          }
19858        "#
19859        .unindent(),
19860    );
19861}
19862
19863#[gpui::test]
19864async fn test_diff_base_change_with_expanded_diff_hunks(
19865    executor: BackgroundExecutor,
19866    cx: &mut TestAppContext,
19867) {
19868    init_test(cx, |_| {});
19869
19870    let mut cx = EditorTestContext::new(cx).await;
19871
19872    let diff_base = r#"
19873        use some::mod1;
19874        use some::mod2;
19875
19876        const A: u32 = 42;
19877        const B: u32 = 42;
19878        const C: u32 = 42;
19879
19880        fn main() {
19881            println!("hello");
19882
19883            println!("world");
19884        }
19885        "#
19886    .unindent();
19887
19888    cx.set_state(
19889        &r#"
19890        use some::mod2;
19891
19892        const A: u32 = 42;
19893        const C: u32 = 42;
19894
19895        fn main(ˇ) {
19896            //println!("hello");
19897
19898            println!("world");
19899            //
19900            //
19901        }
19902        "#
19903        .unindent(),
19904    );
19905
19906    cx.set_head_text(&diff_base);
19907    executor.run_until_parked();
19908
19909    cx.update_editor(|editor, window, cx| {
19910        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19911    });
19912    executor.run_until_parked();
19913    cx.assert_state_with_diff(
19914        r#"
19915        - use some::mod1;
19916          use some::mod2;
19917
19918          const A: u32 = 42;
19919        - const B: u32 = 42;
19920          const C: u32 = 42;
19921
19922          fn main(ˇ) {
19923        -     println!("hello");
19924        +     //println!("hello");
19925
19926              println!("world");
19927        +     //
19928        +     //
19929          }
19930        "#
19931        .unindent(),
19932    );
19933
19934    cx.set_head_text("new diff base!");
19935    executor.run_until_parked();
19936    cx.assert_state_with_diff(
19937        r#"
19938        - new diff base!
19939        + use some::mod2;
19940        +
19941        + const A: u32 = 42;
19942        + const C: u32 = 42;
19943        +
19944        + fn main(ˇ) {
19945        +     //println!("hello");
19946        +
19947        +     println!("world");
19948        +     //
19949        +     //
19950        + }
19951        "#
19952        .unindent(),
19953    );
19954}
19955
19956#[gpui::test]
19957async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19958    init_test(cx, |_| {});
19959
19960    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19961    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19962    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19963    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19964    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19965    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19966
19967    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19968    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19969    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19970
19971    let multi_buffer = cx.new(|cx| {
19972        let mut multibuffer = MultiBuffer::new(ReadWrite);
19973        multibuffer.push_excerpts(
19974            buffer_1.clone(),
19975            [
19976                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19977                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19978                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19979            ],
19980            cx,
19981        );
19982        multibuffer.push_excerpts(
19983            buffer_2.clone(),
19984            [
19985                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19986                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19987                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19988            ],
19989            cx,
19990        );
19991        multibuffer.push_excerpts(
19992            buffer_3.clone(),
19993            [
19994                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19995                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19996                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19997            ],
19998            cx,
19999        );
20000        multibuffer
20001    });
20002
20003    let editor =
20004        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20005    editor
20006        .update(cx, |editor, _window, cx| {
20007            for (buffer, diff_base) in [
20008                (buffer_1.clone(), file_1_old),
20009                (buffer_2.clone(), file_2_old),
20010                (buffer_3.clone(), file_3_old),
20011            ] {
20012                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20013                editor
20014                    .buffer
20015                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20016            }
20017        })
20018        .unwrap();
20019
20020    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20021    cx.run_until_parked();
20022
20023    cx.assert_editor_state(
20024        &"
20025            ˇaaa
20026            ccc
20027            ddd
20028
20029            ggg
20030            hhh
20031
20032
20033            lll
20034            mmm
20035            NNN
20036
20037            qqq
20038            rrr
20039
20040            uuu
20041            111
20042            222
20043            333
20044
20045            666
20046            777
20047
20048            000
20049            !!!"
20050        .unindent(),
20051    );
20052
20053    cx.update_editor(|editor, window, cx| {
20054        editor.select_all(&SelectAll, window, cx);
20055        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20056    });
20057    cx.executor().run_until_parked();
20058
20059    cx.assert_state_with_diff(
20060        "
20061            «aaa
20062          - bbb
20063            ccc
20064            ddd
20065
20066            ggg
20067            hhh
20068
20069
20070            lll
20071            mmm
20072          - nnn
20073          + NNN
20074
20075            qqq
20076            rrr
20077
20078            uuu
20079            111
20080            222
20081            333
20082
20083          + 666
20084            777
20085
20086            000
20087            !!!ˇ»"
20088            .unindent(),
20089    );
20090}
20091
20092#[gpui::test]
20093async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20094    init_test(cx, |_| {});
20095
20096    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20097    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20098
20099    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20100    let multi_buffer = cx.new(|cx| {
20101        let mut multibuffer = MultiBuffer::new(ReadWrite);
20102        multibuffer.push_excerpts(
20103            buffer.clone(),
20104            [
20105                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20106                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20107                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20108            ],
20109            cx,
20110        );
20111        multibuffer
20112    });
20113
20114    let editor =
20115        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20116    editor
20117        .update(cx, |editor, _window, cx| {
20118            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20119            editor
20120                .buffer
20121                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20122        })
20123        .unwrap();
20124
20125    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20126    cx.run_until_parked();
20127
20128    cx.update_editor(|editor, window, cx| {
20129        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20130    });
20131    cx.executor().run_until_parked();
20132
20133    // When the start of a hunk coincides with the start of its excerpt,
20134    // the hunk is expanded. When the start of a hunk is earlier than
20135    // the start of its excerpt, the hunk is not expanded.
20136    cx.assert_state_with_diff(
20137        "
20138            ˇaaa
20139          - bbb
20140          + BBB
20141
20142          - ddd
20143          - eee
20144          + DDD
20145          + EEE
20146            fff
20147
20148            iii
20149        "
20150        .unindent(),
20151    );
20152}
20153
20154#[gpui::test]
20155async fn test_edits_around_expanded_insertion_hunks(
20156    executor: BackgroundExecutor,
20157    cx: &mut TestAppContext,
20158) {
20159    init_test(cx, |_| {});
20160
20161    let mut cx = EditorTestContext::new(cx).await;
20162
20163    let diff_base = r#"
20164        use some::mod1;
20165        use some::mod2;
20166
20167        const A: u32 = 42;
20168
20169        fn main() {
20170            println!("hello");
20171
20172            println!("world");
20173        }
20174        "#
20175    .unindent();
20176    executor.run_until_parked();
20177    cx.set_state(
20178        &r#"
20179        use some::mod1;
20180        use some::mod2;
20181
20182        const A: u32 = 42;
20183        const B: u32 = 42;
20184        const C: u32 = 42;
20185        ˇ
20186
20187        fn main() {
20188            println!("hello");
20189
20190            println!("world");
20191        }
20192        "#
20193        .unindent(),
20194    );
20195
20196    cx.set_head_text(&diff_base);
20197    executor.run_until_parked();
20198
20199    cx.update_editor(|editor, window, cx| {
20200        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20201    });
20202    executor.run_until_parked();
20203
20204    cx.assert_state_with_diff(
20205        r#"
20206        use some::mod1;
20207        use some::mod2;
20208
20209        const A: u32 = 42;
20210      + const B: u32 = 42;
20211      + const C: u32 = 42;
20212      + ˇ
20213
20214        fn main() {
20215            println!("hello");
20216
20217            println!("world");
20218        }
20219      "#
20220        .unindent(),
20221    );
20222
20223    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20224    executor.run_until_parked();
20225
20226    cx.assert_state_with_diff(
20227        r#"
20228        use some::mod1;
20229        use some::mod2;
20230
20231        const A: u32 = 42;
20232      + const B: u32 = 42;
20233      + const C: u32 = 42;
20234      + const D: u32 = 42;
20235      + ˇ
20236
20237        fn main() {
20238            println!("hello");
20239
20240            println!("world");
20241        }
20242      "#
20243        .unindent(),
20244    );
20245
20246    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20247    executor.run_until_parked();
20248
20249    cx.assert_state_with_diff(
20250        r#"
20251        use some::mod1;
20252        use some::mod2;
20253
20254        const A: u32 = 42;
20255      + const B: u32 = 42;
20256      + const C: u32 = 42;
20257      + const D: u32 = 42;
20258      + const E: u32 = 42;
20259      + ˇ
20260
20261        fn main() {
20262            println!("hello");
20263
20264            println!("world");
20265        }
20266      "#
20267        .unindent(),
20268    );
20269
20270    cx.update_editor(|editor, window, cx| {
20271        editor.delete_line(&DeleteLine, window, cx);
20272    });
20273    executor.run_until_parked();
20274
20275    cx.assert_state_with_diff(
20276        r#"
20277        use some::mod1;
20278        use some::mod2;
20279
20280        const A: u32 = 42;
20281      + const B: u32 = 42;
20282      + const C: u32 = 42;
20283      + const D: u32 = 42;
20284      + const E: u32 = 42;
20285        ˇ
20286        fn main() {
20287            println!("hello");
20288
20289            println!("world");
20290        }
20291      "#
20292        .unindent(),
20293    );
20294
20295    cx.update_editor(|editor, window, cx| {
20296        editor.move_up(&MoveUp, window, cx);
20297        editor.delete_line(&DeleteLine, window, cx);
20298        editor.move_up(&MoveUp, window, cx);
20299        editor.delete_line(&DeleteLine, window, cx);
20300        editor.move_up(&MoveUp, window, cx);
20301        editor.delete_line(&DeleteLine, window, cx);
20302    });
20303    executor.run_until_parked();
20304    cx.assert_state_with_diff(
20305        r#"
20306        use some::mod1;
20307        use some::mod2;
20308
20309        const A: u32 = 42;
20310      + const B: u32 = 42;
20311        ˇ
20312        fn main() {
20313            println!("hello");
20314
20315            println!("world");
20316        }
20317      "#
20318        .unindent(),
20319    );
20320
20321    cx.update_editor(|editor, window, cx| {
20322        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20323        editor.delete_line(&DeleteLine, window, cx);
20324    });
20325    executor.run_until_parked();
20326    cx.assert_state_with_diff(
20327        r#"
20328        ˇ
20329        fn main() {
20330            println!("hello");
20331
20332            println!("world");
20333        }
20334      "#
20335        .unindent(),
20336    );
20337}
20338
20339#[gpui::test]
20340async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20341    init_test(cx, |_| {});
20342
20343    let mut cx = EditorTestContext::new(cx).await;
20344    cx.set_head_text(indoc! { "
20345        one
20346        two
20347        three
20348        four
20349        five
20350        "
20351    });
20352    cx.set_state(indoc! { "
20353        one
20354        ˇthree
20355        five
20356    "});
20357    cx.run_until_parked();
20358    cx.update_editor(|editor, window, cx| {
20359        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20360    });
20361    cx.assert_state_with_diff(
20362        indoc! { "
20363        one
20364      - two
20365        ˇthree
20366      - four
20367        five
20368    "}
20369        .to_string(),
20370    );
20371    cx.update_editor(|editor, window, cx| {
20372        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20373    });
20374
20375    cx.assert_state_with_diff(
20376        indoc! { "
20377        one
20378        ˇthree
20379        five
20380    "}
20381        .to_string(),
20382    );
20383
20384    cx.set_state(indoc! { "
20385        one
20386        ˇTWO
20387        three
20388        four
20389        five
20390    "});
20391    cx.run_until_parked();
20392    cx.update_editor(|editor, window, cx| {
20393        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20394    });
20395
20396    cx.assert_state_with_diff(
20397        indoc! { "
20398            one
20399          - two
20400          + ˇTWO
20401            three
20402            four
20403            five
20404        "}
20405        .to_string(),
20406    );
20407    cx.update_editor(|editor, window, cx| {
20408        editor.move_up(&Default::default(), window, cx);
20409        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20410    });
20411    cx.assert_state_with_diff(
20412        indoc! { "
20413            one
20414            ˇTWO
20415            three
20416            four
20417            five
20418        "}
20419        .to_string(),
20420    );
20421}
20422
20423#[gpui::test]
20424async fn test_edits_around_expanded_deletion_hunks(
20425    executor: BackgroundExecutor,
20426    cx: &mut TestAppContext,
20427) {
20428    init_test(cx, |_| {});
20429
20430    let mut cx = EditorTestContext::new(cx).await;
20431
20432    let diff_base = r#"
20433        use some::mod1;
20434        use some::mod2;
20435
20436        const A: u32 = 42;
20437        const B: u32 = 42;
20438        const C: u32 = 42;
20439
20440
20441        fn main() {
20442            println!("hello");
20443
20444            println!("world");
20445        }
20446    "#
20447    .unindent();
20448    executor.run_until_parked();
20449    cx.set_state(
20450        &r#"
20451        use some::mod1;
20452        use some::mod2;
20453
20454        ˇconst B: u32 = 42;
20455        const C: u32 = 42;
20456
20457
20458        fn main() {
20459            println!("hello");
20460
20461            println!("world");
20462        }
20463        "#
20464        .unindent(),
20465    );
20466
20467    cx.set_head_text(&diff_base);
20468    executor.run_until_parked();
20469
20470    cx.update_editor(|editor, window, cx| {
20471        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20472    });
20473    executor.run_until_parked();
20474
20475    cx.assert_state_with_diff(
20476        r#"
20477        use some::mod1;
20478        use some::mod2;
20479
20480      - const A: u32 = 42;
20481        ˇconst B: u32 = 42;
20482        const C: u32 = 42;
20483
20484
20485        fn main() {
20486            println!("hello");
20487
20488            println!("world");
20489        }
20490      "#
20491        .unindent(),
20492    );
20493
20494    cx.update_editor(|editor, window, cx| {
20495        editor.delete_line(&DeleteLine, window, cx);
20496    });
20497    executor.run_until_parked();
20498    cx.assert_state_with_diff(
20499        r#"
20500        use some::mod1;
20501        use some::mod2;
20502
20503      - const A: u32 = 42;
20504      - const B: u32 = 42;
20505        ˇconst C: u32 = 42;
20506
20507
20508        fn main() {
20509            println!("hello");
20510
20511            println!("world");
20512        }
20513      "#
20514        .unindent(),
20515    );
20516
20517    cx.update_editor(|editor, window, cx| {
20518        editor.delete_line(&DeleteLine, window, cx);
20519    });
20520    executor.run_until_parked();
20521    cx.assert_state_with_diff(
20522        r#"
20523        use some::mod1;
20524        use some::mod2;
20525
20526      - const A: u32 = 42;
20527      - const B: u32 = 42;
20528      - const C: u32 = 42;
20529        ˇ
20530
20531        fn main() {
20532            println!("hello");
20533
20534            println!("world");
20535        }
20536      "#
20537        .unindent(),
20538    );
20539
20540    cx.update_editor(|editor, window, cx| {
20541        editor.handle_input("replacement", window, cx);
20542    });
20543    executor.run_until_parked();
20544    cx.assert_state_with_diff(
20545        r#"
20546        use some::mod1;
20547        use some::mod2;
20548
20549      - const A: u32 = 42;
20550      - const B: u32 = 42;
20551      - const C: u32 = 42;
20552      -
20553      + replacementˇ
20554
20555        fn main() {
20556            println!("hello");
20557
20558            println!("world");
20559        }
20560      "#
20561        .unindent(),
20562    );
20563}
20564
20565#[gpui::test]
20566async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20567    init_test(cx, |_| {});
20568
20569    let mut cx = EditorTestContext::new(cx).await;
20570
20571    let base_text = r#"
20572        one
20573        two
20574        three
20575        four
20576        five
20577    "#
20578    .unindent();
20579    executor.run_until_parked();
20580    cx.set_state(
20581        &r#"
20582        one
20583        two
20584        fˇour
20585        five
20586        "#
20587        .unindent(),
20588    );
20589
20590    cx.set_head_text(&base_text);
20591    executor.run_until_parked();
20592
20593    cx.update_editor(|editor, window, cx| {
20594        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20595    });
20596    executor.run_until_parked();
20597
20598    cx.assert_state_with_diff(
20599        r#"
20600          one
20601          two
20602        - three
20603          fˇour
20604          five
20605        "#
20606        .unindent(),
20607    );
20608
20609    cx.update_editor(|editor, window, cx| {
20610        editor.backspace(&Backspace, window, cx);
20611        editor.backspace(&Backspace, window, cx);
20612    });
20613    executor.run_until_parked();
20614    cx.assert_state_with_diff(
20615        r#"
20616          one
20617          two
20618        - threeˇ
20619        - four
20620        + our
20621          five
20622        "#
20623        .unindent(),
20624    );
20625}
20626
20627#[gpui::test]
20628async fn test_edit_after_expanded_modification_hunk(
20629    executor: BackgroundExecutor,
20630    cx: &mut TestAppContext,
20631) {
20632    init_test(cx, |_| {});
20633
20634    let mut cx = EditorTestContext::new(cx).await;
20635
20636    let diff_base = r#"
20637        use some::mod1;
20638        use some::mod2;
20639
20640        const A: u32 = 42;
20641        const B: u32 = 42;
20642        const C: u32 = 42;
20643        const D: u32 = 42;
20644
20645
20646        fn main() {
20647            println!("hello");
20648
20649            println!("world");
20650        }"#
20651    .unindent();
20652
20653    cx.set_state(
20654        &r#"
20655        use some::mod1;
20656        use some::mod2;
20657
20658        const A: u32 = 42;
20659        const B: u32 = 42;
20660        const C: u32 = 43ˇ
20661        const D: u32 = 42;
20662
20663
20664        fn main() {
20665            println!("hello");
20666
20667            println!("world");
20668        }"#
20669        .unindent(),
20670    );
20671
20672    cx.set_head_text(&diff_base);
20673    executor.run_until_parked();
20674    cx.update_editor(|editor, window, cx| {
20675        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20676    });
20677    executor.run_until_parked();
20678
20679    cx.assert_state_with_diff(
20680        r#"
20681        use some::mod1;
20682        use some::mod2;
20683
20684        const A: u32 = 42;
20685        const B: u32 = 42;
20686      - const C: u32 = 42;
20687      + const C: u32 = 43ˇ
20688        const D: u32 = 42;
20689
20690
20691        fn main() {
20692            println!("hello");
20693
20694            println!("world");
20695        }"#
20696        .unindent(),
20697    );
20698
20699    cx.update_editor(|editor, window, cx| {
20700        editor.handle_input("\nnew_line\n", window, cx);
20701    });
20702    executor.run_until_parked();
20703
20704    cx.assert_state_with_diff(
20705        r#"
20706        use some::mod1;
20707        use some::mod2;
20708
20709        const A: u32 = 42;
20710        const B: u32 = 42;
20711      - const C: u32 = 42;
20712      + const C: u32 = 43
20713      + new_line
20714      + ˇ
20715        const D: u32 = 42;
20716
20717
20718        fn main() {
20719            println!("hello");
20720
20721            println!("world");
20722        }"#
20723        .unindent(),
20724    );
20725}
20726
20727#[gpui::test]
20728async fn test_stage_and_unstage_added_file_hunk(
20729    executor: BackgroundExecutor,
20730    cx: &mut TestAppContext,
20731) {
20732    init_test(cx, |_| {});
20733
20734    let mut cx = EditorTestContext::new(cx).await;
20735    cx.update_editor(|editor, _, cx| {
20736        editor.set_expand_all_diff_hunks(cx);
20737    });
20738
20739    let working_copy = r#"
20740            ˇfn main() {
20741                println!("hello, world!");
20742            }
20743        "#
20744    .unindent();
20745
20746    cx.set_state(&working_copy);
20747    executor.run_until_parked();
20748
20749    cx.assert_state_with_diff(
20750        r#"
20751            + ˇfn main() {
20752            +     println!("hello, world!");
20753            + }
20754        "#
20755        .unindent(),
20756    );
20757    cx.assert_index_text(None);
20758
20759    cx.update_editor(|editor, window, cx| {
20760        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20761    });
20762    executor.run_until_parked();
20763    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20764    cx.assert_state_with_diff(
20765        r#"
20766            + ˇfn main() {
20767            +     println!("hello, world!");
20768            + }
20769        "#
20770        .unindent(),
20771    );
20772
20773    cx.update_editor(|editor, window, cx| {
20774        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20775    });
20776    executor.run_until_parked();
20777    cx.assert_index_text(None);
20778}
20779
20780async fn setup_indent_guides_editor(
20781    text: &str,
20782    cx: &mut TestAppContext,
20783) -> (BufferId, EditorTestContext) {
20784    init_test(cx, |_| {});
20785
20786    let mut cx = EditorTestContext::new(cx).await;
20787
20788    let buffer_id = cx.update_editor(|editor, window, cx| {
20789        editor.set_text(text, window, cx);
20790        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20791
20792        buffer_ids[0]
20793    });
20794
20795    (buffer_id, cx)
20796}
20797
20798fn assert_indent_guides(
20799    range: Range<u32>,
20800    expected: Vec<IndentGuide>,
20801    active_indices: Option<Vec<usize>>,
20802    cx: &mut EditorTestContext,
20803) {
20804    let indent_guides = cx.update_editor(|editor, window, cx| {
20805        let snapshot = editor.snapshot(window, cx).display_snapshot;
20806        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20807            editor,
20808            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20809            true,
20810            &snapshot,
20811            cx,
20812        );
20813
20814        indent_guides.sort_by(|a, b| {
20815            a.depth.cmp(&b.depth).then(
20816                a.start_row
20817                    .cmp(&b.start_row)
20818                    .then(a.end_row.cmp(&b.end_row)),
20819            )
20820        });
20821        indent_guides
20822    });
20823
20824    if let Some(expected) = active_indices {
20825        let active_indices = cx.update_editor(|editor, window, cx| {
20826            let snapshot = editor.snapshot(window, cx).display_snapshot;
20827            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20828        });
20829
20830        assert_eq!(
20831            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20832            expected,
20833            "Active indent guide indices do not match"
20834        );
20835    }
20836
20837    assert_eq!(indent_guides, expected, "Indent guides do not match");
20838}
20839
20840fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20841    IndentGuide {
20842        buffer_id,
20843        start_row: MultiBufferRow(start_row),
20844        end_row: MultiBufferRow(end_row),
20845        depth,
20846        tab_size: 4,
20847        settings: IndentGuideSettings {
20848            enabled: true,
20849            line_width: 1,
20850            active_line_width: 1,
20851            coloring: IndentGuideColoring::default(),
20852            background_coloring: IndentGuideBackgroundColoring::default(),
20853        },
20854    }
20855}
20856
20857#[gpui::test]
20858async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20859    let (buffer_id, mut cx) = setup_indent_guides_editor(
20860        &"
20861        fn main() {
20862            let a = 1;
20863        }"
20864        .unindent(),
20865        cx,
20866    )
20867    .await;
20868
20869    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20870}
20871
20872#[gpui::test]
20873async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20874    let (buffer_id, mut cx) = setup_indent_guides_editor(
20875        &"
20876        fn main() {
20877            let a = 1;
20878            let b = 2;
20879        }"
20880        .unindent(),
20881        cx,
20882    )
20883    .await;
20884
20885    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20886}
20887
20888#[gpui::test]
20889async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20890    let (buffer_id, mut cx) = setup_indent_guides_editor(
20891        &"
20892        fn main() {
20893            let a = 1;
20894            if a == 3 {
20895                let b = 2;
20896            } else {
20897                let c = 3;
20898            }
20899        }"
20900        .unindent(),
20901        cx,
20902    )
20903    .await;
20904
20905    assert_indent_guides(
20906        0..8,
20907        vec![
20908            indent_guide(buffer_id, 1, 6, 0),
20909            indent_guide(buffer_id, 3, 3, 1),
20910            indent_guide(buffer_id, 5, 5, 1),
20911        ],
20912        None,
20913        &mut cx,
20914    );
20915}
20916
20917#[gpui::test]
20918async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20919    let (buffer_id, mut cx) = setup_indent_guides_editor(
20920        &"
20921        fn main() {
20922            let a = 1;
20923                let b = 2;
20924            let c = 3;
20925        }"
20926        .unindent(),
20927        cx,
20928    )
20929    .await;
20930
20931    assert_indent_guides(
20932        0..5,
20933        vec![
20934            indent_guide(buffer_id, 1, 3, 0),
20935            indent_guide(buffer_id, 2, 2, 1),
20936        ],
20937        None,
20938        &mut cx,
20939    );
20940}
20941
20942#[gpui::test]
20943async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20944    let (buffer_id, mut cx) = setup_indent_guides_editor(
20945        &"
20946        fn main() {
20947            let a = 1;
20948
20949            let c = 3;
20950        }"
20951        .unindent(),
20952        cx,
20953    )
20954    .await;
20955
20956    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20957}
20958
20959#[gpui::test]
20960async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20961    let (buffer_id, mut cx) = setup_indent_guides_editor(
20962        &"
20963        fn main() {
20964            let a = 1;
20965
20966            let c = 3;
20967
20968            if a == 3 {
20969                let b = 2;
20970            } else {
20971                let c = 3;
20972            }
20973        }"
20974        .unindent(),
20975        cx,
20976    )
20977    .await;
20978
20979    assert_indent_guides(
20980        0..11,
20981        vec![
20982            indent_guide(buffer_id, 1, 9, 0),
20983            indent_guide(buffer_id, 6, 6, 1),
20984            indent_guide(buffer_id, 8, 8, 1),
20985        ],
20986        None,
20987        &mut cx,
20988    );
20989}
20990
20991#[gpui::test]
20992async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20993    let (buffer_id, mut cx) = setup_indent_guides_editor(
20994        &"
20995        fn main() {
20996            let a = 1;
20997
20998            let c = 3;
20999
21000            if a == 3 {
21001                let b = 2;
21002            } else {
21003                let c = 3;
21004            }
21005        }"
21006        .unindent(),
21007        cx,
21008    )
21009    .await;
21010
21011    assert_indent_guides(
21012        1..11,
21013        vec![
21014            indent_guide(buffer_id, 1, 9, 0),
21015            indent_guide(buffer_id, 6, 6, 1),
21016            indent_guide(buffer_id, 8, 8, 1),
21017        ],
21018        None,
21019        &mut cx,
21020    );
21021}
21022
21023#[gpui::test]
21024async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21025    let (buffer_id, mut cx) = setup_indent_guides_editor(
21026        &"
21027        fn main() {
21028            let a = 1;
21029
21030            let c = 3;
21031
21032            if a == 3 {
21033                let b = 2;
21034            } else {
21035                let c = 3;
21036            }
21037        }"
21038        .unindent(),
21039        cx,
21040    )
21041    .await;
21042
21043    assert_indent_guides(
21044        1..10,
21045        vec![
21046            indent_guide(buffer_id, 1, 9, 0),
21047            indent_guide(buffer_id, 6, 6, 1),
21048            indent_guide(buffer_id, 8, 8, 1),
21049        ],
21050        None,
21051        &mut cx,
21052    );
21053}
21054
21055#[gpui::test]
21056async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21057    let (buffer_id, mut cx) = setup_indent_guides_editor(
21058        &"
21059        fn main() {
21060            if a {
21061                b(
21062                    c,
21063                    d,
21064                )
21065            } else {
21066                e(
21067                    f
21068                )
21069            }
21070        }"
21071        .unindent(),
21072        cx,
21073    )
21074    .await;
21075
21076    assert_indent_guides(
21077        0..11,
21078        vec![
21079            indent_guide(buffer_id, 1, 10, 0),
21080            indent_guide(buffer_id, 2, 5, 1),
21081            indent_guide(buffer_id, 7, 9, 1),
21082            indent_guide(buffer_id, 3, 4, 2),
21083            indent_guide(buffer_id, 8, 8, 2),
21084        ],
21085        None,
21086        &mut cx,
21087    );
21088
21089    cx.update_editor(|editor, window, cx| {
21090        editor.fold_at(MultiBufferRow(2), window, cx);
21091        assert_eq!(
21092            editor.display_text(cx),
21093            "
21094            fn main() {
21095                if a {
21096                    b(⋯
21097                    )
21098                } else {
21099                    e(
21100                        f
21101                    )
21102                }
21103            }"
21104            .unindent()
21105        );
21106    });
21107
21108    assert_indent_guides(
21109        0..11,
21110        vec![
21111            indent_guide(buffer_id, 1, 10, 0),
21112            indent_guide(buffer_id, 2, 5, 1),
21113            indent_guide(buffer_id, 7, 9, 1),
21114            indent_guide(buffer_id, 8, 8, 2),
21115        ],
21116        None,
21117        &mut cx,
21118    );
21119}
21120
21121#[gpui::test]
21122async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21123    let (buffer_id, mut cx) = setup_indent_guides_editor(
21124        &"
21125        block1
21126            block2
21127                block3
21128                    block4
21129            block2
21130        block1
21131        block1"
21132            .unindent(),
21133        cx,
21134    )
21135    .await;
21136
21137    assert_indent_guides(
21138        1..10,
21139        vec![
21140            indent_guide(buffer_id, 1, 4, 0),
21141            indent_guide(buffer_id, 2, 3, 1),
21142            indent_guide(buffer_id, 3, 3, 2),
21143        ],
21144        None,
21145        &mut cx,
21146    );
21147}
21148
21149#[gpui::test]
21150async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21151    let (buffer_id, mut cx) = setup_indent_guides_editor(
21152        &"
21153        block1
21154            block2
21155                block3
21156
21157        block1
21158        block1"
21159            .unindent(),
21160        cx,
21161    )
21162    .await;
21163
21164    assert_indent_guides(
21165        0..6,
21166        vec![
21167            indent_guide(buffer_id, 1, 2, 0),
21168            indent_guide(buffer_id, 2, 2, 1),
21169        ],
21170        None,
21171        &mut cx,
21172    );
21173}
21174
21175#[gpui::test]
21176async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21177    let (buffer_id, mut cx) = setup_indent_guides_editor(
21178        &"
21179        function component() {
21180        \treturn (
21181        \t\t\t
21182        \t\t<div>
21183        \t\t\t<abc></abc>
21184        \t\t</div>
21185        \t)
21186        }"
21187        .unindent(),
21188        cx,
21189    )
21190    .await;
21191
21192    assert_indent_guides(
21193        0..8,
21194        vec![
21195            indent_guide(buffer_id, 1, 6, 0),
21196            indent_guide(buffer_id, 2, 5, 1),
21197            indent_guide(buffer_id, 4, 4, 2),
21198        ],
21199        None,
21200        &mut cx,
21201    );
21202}
21203
21204#[gpui::test]
21205async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21206    let (buffer_id, mut cx) = setup_indent_guides_editor(
21207        &"
21208        function component() {
21209        \treturn (
21210        \t
21211        \t\t<div>
21212        \t\t\t<abc></abc>
21213        \t\t</div>
21214        \t)
21215        }"
21216        .unindent(),
21217        cx,
21218    )
21219    .await;
21220
21221    assert_indent_guides(
21222        0..8,
21223        vec![
21224            indent_guide(buffer_id, 1, 6, 0),
21225            indent_guide(buffer_id, 2, 5, 1),
21226            indent_guide(buffer_id, 4, 4, 2),
21227        ],
21228        None,
21229        &mut cx,
21230    );
21231}
21232
21233#[gpui::test]
21234async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21235    let (buffer_id, mut cx) = setup_indent_guides_editor(
21236        &"
21237        block1
21238
21239
21240
21241            block2
21242        "
21243        .unindent(),
21244        cx,
21245    )
21246    .await;
21247
21248    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21249}
21250
21251#[gpui::test]
21252async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21253    let (buffer_id, mut cx) = setup_indent_guides_editor(
21254        &"
21255        def a:
21256        \tb = 3
21257        \tif True:
21258        \t\tc = 4
21259        \t\td = 5
21260        \tprint(b)
21261        "
21262        .unindent(),
21263        cx,
21264    )
21265    .await;
21266
21267    assert_indent_guides(
21268        0..6,
21269        vec![
21270            indent_guide(buffer_id, 1, 5, 0),
21271            indent_guide(buffer_id, 3, 4, 1),
21272        ],
21273        None,
21274        &mut cx,
21275    );
21276}
21277
21278#[gpui::test]
21279async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21280    let (buffer_id, mut cx) = setup_indent_guides_editor(
21281        &"
21282    fn main() {
21283        let a = 1;
21284    }"
21285        .unindent(),
21286        cx,
21287    )
21288    .await;
21289
21290    cx.update_editor(|editor, window, cx| {
21291        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21292            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21293        });
21294    });
21295
21296    assert_indent_guides(
21297        0..3,
21298        vec![indent_guide(buffer_id, 1, 1, 0)],
21299        Some(vec![0]),
21300        &mut cx,
21301    );
21302}
21303
21304#[gpui::test]
21305async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21306    let (buffer_id, mut cx) = setup_indent_guides_editor(
21307        &"
21308    fn main() {
21309        if 1 == 2 {
21310            let a = 1;
21311        }
21312    }"
21313        .unindent(),
21314        cx,
21315    )
21316    .await;
21317
21318    cx.update_editor(|editor, window, cx| {
21319        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21320            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21321        });
21322    });
21323
21324    assert_indent_guides(
21325        0..4,
21326        vec![
21327            indent_guide(buffer_id, 1, 3, 0),
21328            indent_guide(buffer_id, 2, 2, 1),
21329        ],
21330        Some(vec![1]),
21331        &mut cx,
21332    );
21333
21334    cx.update_editor(|editor, window, cx| {
21335        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21336            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21337        });
21338    });
21339
21340    assert_indent_guides(
21341        0..4,
21342        vec![
21343            indent_guide(buffer_id, 1, 3, 0),
21344            indent_guide(buffer_id, 2, 2, 1),
21345        ],
21346        Some(vec![1]),
21347        &mut cx,
21348    );
21349
21350    cx.update_editor(|editor, window, cx| {
21351        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21352            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21353        });
21354    });
21355
21356    assert_indent_guides(
21357        0..4,
21358        vec![
21359            indent_guide(buffer_id, 1, 3, 0),
21360            indent_guide(buffer_id, 2, 2, 1),
21361        ],
21362        Some(vec![0]),
21363        &mut cx,
21364    );
21365}
21366
21367#[gpui::test]
21368async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21369    let (buffer_id, mut cx) = setup_indent_guides_editor(
21370        &"
21371    fn main() {
21372        let a = 1;
21373
21374        let b = 2;
21375    }"
21376        .unindent(),
21377        cx,
21378    )
21379    .await;
21380
21381    cx.update_editor(|editor, window, cx| {
21382        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21383            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21384        });
21385    });
21386
21387    assert_indent_guides(
21388        0..5,
21389        vec![indent_guide(buffer_id, 1, 3, 0)],
21390        Some(vec![0]),
21391        &mut cx,
21392    );
21393}
21394
21395#[gpui::test]
21396async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21397    let (buffer_id, mut cx) = setup_indent_guides_editor(
21398        &"
21399    def m:
21400        a = 1
21401        pass"
21402            .unindent(),
21403        cx,
21404    )
21405    .await;
21406
21407    cx.update_editor(|editor, window, cx| {
21408        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21409            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21410        });
21411    });
21412
21413    assert_indent_guides(
21414        0..3,
21415        vec![indent_guide(buffer_id, 1, 2, 0)],
21416        Some(vec![0]),
21417        &mut cx,
21418    );
21419}
21420
21421#[gpui::test]
21422async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21423    init_test(cx, |_| {});
21424    let mut cx = EditorTestContext::new(cx).await;
21425    let text = indoc! {
21426        "
21427        impl A {
21428            fn b() {
21429                0;
21430                3;
21431                5;
21432                6;
21433                7;
21434            }
21435        }
21436        "
21437    };
21438    let base_text = indoc! {
21439        "
21440        impl A {
21441            fn b() {
21442                0;
21443                1;
21444                2;
21445                3;
21446                4;
21447            }
21448            fn c() {
21449                5;
21450                6;
21451                7;
21452            }
21453        }
21454        "
21455    };
21456
21457    cx.update_editor(|editor, window, cx| {
21458        editor.set_text(text, window, cx);
21459
21460        editor.buffer().update(cx, |multibuffer, cx| {
21461            let buffer = multibuffer.as_singleton().unwrap();
21462            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21463
21464            multibuffer.set_all_diff_hunks_expanded(cx);
21465            multibuffer.add_diff(diff, cx);
21466
21467            buffer.read(cx).remote_id()
21468        })
21469    });
21470    cx.run_until_parked();
21471
21472    cx.assert_state_with_diff(
21473        indoc! { "
21474          impl A {
21475              fn b() {
21476                  0;
21477        -         1;
21478        -         2;
21479                  3;
21480        -         4;
21481        -     }
21482        -     fn c() {
21483                  5;
21484                  6;
21485                  7;
21486              }
21487          }
21488          ˇ"
21489        }
21490        .to_string(),
21491    );
21492
21493    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21494        editor
21495            .snapshot(window, cx)
21496            .buffer_snapshot()
21497            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21498            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21499            .collect::<Vec<_>>()
21500    });
21501    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21502    assert_eq!(
21503        actual_guides,
21504        vec![
21505            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21506            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21507            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21508        ]
21509    );
21510}
21511
21512#[gpui::test]
21513async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21514    init_test(cx, |_| {});
21515    let mut cx = EditorTestContext::new(cx).await;
21516
21517    let diff_base = r#"
21518        a
21519        b
21520        c
21521        "#
21522    .unindent();
21523
21524    cx.set_state(
21525        &r#"
21526        ˇA
21527        b
21528        C
21529        "#
21530        .unindent(),
21531    );
21532    cx.set_head_text(&diff_base);
21533    cx.update_editor(|editor, window, cx| {
21534        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21535    });
21536    executor.run_until_parked();
21537
21538    let both_hunks_expanded = r#"
21539        - a
21540        + ˇA
21541          b
21542        - c
21543        + C
21544        "#
21545    .unindent();
21546
21547    cx.assert_state_with_diff(both_hunks_expanded.clone());
21548
21549    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21550        let snapshot = editor.snapshot(window, cx);
21551        let hunks = editor
21552            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21553            .collect::<Vec<_>>();
21554        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21555        hunks
21556            .into_iter()
21557            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21558            .collect::<Vec<_>>()
21559    });
21560    assert_eq!(hunk_ranges.len(), 2);
21561
21562    cx.update_editor(|editor, _, cx| {
21563        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21564    });
21565    executor.run_until_parked();
21566
21567    let second_hunk_expanded = r#"
21568          ˇA
21569          b
21570        - c
21571        + C
21572        "#
21573    .unindent();
21574
21575    cx.assert_state_with_diff(second_hunk_expanded);
21576
21577    cx.update_editor(|editor, _, cx| {
21578        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21579    });
21580    executor.run_until_parked();
21581
21582    cx.assert_state_with_diff(both_hunks_expanded.clone());
21583
21584    cx.update_editor(|editor, _, cx| {
21585        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21586    });
21587    executor.run_until_parked();
21588
21589    let first_hunk_expanded = r#"
21590        - a
21591        + ˇA
21592          b
21593          C
21594        "#
21595    .unindent();
21596
21597    cx.assert_state_with_diff(first_hunk_expanded);
21598
21599    cx.update_editor(|editor, _, cx| {
21600        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21601    });
21602    executor.run_until_parked();
21603
21604    cx.assert_state_with_diff(both_hunks_expanded);
21605
21606    cx.set_state(
21607        &r#"
21608        ˇA
21609        b
21610        "#
21611        .unindent(),
21612    );
21613    cx.run_until_parked();
21614
21615    // TODO this cursor position seems bad
21616    cx.assert_state_with_diff(
21617        r#"
21618        - ˇa
21619        + A
21620          b
21621        "#
21622        .unindent(),
21623    );
21624
21625    cx.update_editor(|editor, window, cx| {
21626        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21627    });
21628
21629    cx.assert_state_with_diff(
21630        r#"
21631            - ˇa
21632            + A
21633              b
21634            - c
21635            "#
21636        .unindent(),
21637    );
21638
21639    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21640        let snapshot = editor.snapshot(window, cx);
21641        let hunks = editor
21642            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21643            .collect::<Vec<_>>();
21644        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21645        hunks
21646            .into_iter()
21647            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21648            .collect::<Vec<_>>()
21649    });
21650    assert_eq!(hunk_ranges.len(), 2);
21651
21652    cx.update_editor(|editor, _, cx| {
21653        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21654    });
21655    executor.run_until_parked();
21656
21657    cx.assert_state_with_diff(
21658        r#"
21659        - ˇa
21660        + A
21661          b
21662        "#
21663        .unindent(),
21664    );
21665}
21666
21667#[gpui::test]
21668async fn test_toggle_deletion_hunk_at_start_of_file(
21669    executor: BackgroundExecutor,
21670    cx: &mut TestAppContext,
21671) {
21672    init_test(cx, |_| {});
21673    let mut cx = EditorTestContext::new(cx).await;
21674
21675    let diff_base = r#"
21676        a
21677        b
21678        c
21679        "#
21680    .unindent();
21681
21682    cx.set_state(
21683        &r#"
21684        ˇb
21685        c
21686        "#
21687        .unindent(),
21688    );
21689    cx.set_head_text(&diff_base);
21690    cx.update_editor(|editor, window, cx| {
21691        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21692    });
21693    executor.run_until_parked();
21694
21695    let hunk_expanded = r#"
21696        - a
21697          ˇb
21698          c
21699        "#
21700    .unindent();
21701
21702    cx.assert_state_with_diff(hunk_expanded.clone());
21703
21704    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21705        let snapshot = editor.snapshot(window, cx);
21706        let hunks = editor
21707            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21708            .collect::<Vec<_>>();
21709        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21710        hunks
21711            .into_iter()
21712            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21713            .collect::<Vec<_>>()
21714    });
21715    assert_eq!(hunk_ranges.len(), 1);
21716
21717    cx.update_editor(|editor, _, cx| {
21718        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21719    });
21720    executor.run_until_parked();
21721
21722    let hunk_collapsed = r#"
21723          ˇb
21724          c
21725        "#
21726    .unindent();
21727
21728    cx.assert_state_with_diff(hunk_collapsed);
21729
21730    cx.update_editor(|editor, _, cx| {
21731        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21732    });
21733    executor.run_until_parked();
21734
21735    cx.assert_state_with_diff(hunk_expanded);
21736}
21737
21738#[gpui::test]
21739async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21740    init_test(cx, |_| {});
21741
21742    let fs = FakeFs::new(cx.executor());
21743    fs.insert_tree(
21744        path!("/test"),
21745        json!({
21746            ".git": {},
21747            "file-1": "ONE\n",
21748            "file-2": "TWO\n",
21749            "file-3": "THREE\n",
21750        }),
21751    )
21752    .await;
21753
21754    fs.set_head_for_repo(
21755        path!("/test/.git").as_ref(),
21756        &[
21757            ("file-1", "one\n".into()),
21758            ("file-2", "two\n".into()),
21759            ("file-3", "three\n".into()),
21760        ],
21761        "deadbeef",
21762    );
21763
21764    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21765    let mut buffers = vec![];
21766    for i in 1..=3 {
21767        let buffer = project
21768            .update(cx, |project, cx| {
21769                let path = format!(path!("/test/file-{}"), i);
21770                project.open_local_buffer(path, cx)
21771            })
21772            .await
21773            .unwrap();
21774        buffers.push(buffer);
21775    }
21776
21777    let multibuffer = cx.new(|cx| {
21778        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21779        multibuffer.set_all_diff_hunks_expanded(cx);
21780        for buffer in &buffers {
21781            let snapshot = buffer.read(cx).snapshot();
21782            multibuffer.set_excerpts_for_path(
21783                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21784                buffer.clone(),
21785                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21786                2,
21787                cx,
21788            );
21789        }
21790        multibuffer
21791    });
21792
21793    let editor = cx.add_window(|window, cx| {
21794        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21795    });
21796    cx.run_until_parked();
21797
21798    let snapshot = editor
21799        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21800        .unwrap();
21801    let hunks = snapshot
21802        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21803        .map(|hunk| match hunk {
21804            DisplayDiffHunk::Unfolded {
21805                display_row_range, ..
21806            } => display_row_range,
21807            DisplayDiffHunk::Folded { .. } => unreachable!(),
21808        })
21809        .collect::<Vec<_>>();
21810    assert_eq!(
21811        hunks,
21812        [
21813            DisplayRow(2)..DisplayRow(4),
21814            DisplayRow(7)..DisplayRow(9),
21815            DisplayRow(12)..DisplayRow(14),
21816        ]
21817    );
21818}
21819
21820#[gpui::test]
21821async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21822    init_test(cx, |_| {});
21823
21824    let mut cx = EditorTestContext::new(cx).await;
21825    cx.set_head_text(indoc! { "
21826        one
21827        two
21828        three
21829        four
21830        five
21831        "
21832    });
21833    cx.set_index_text(indoc! { "
21834        one
21835        two
21836        three
21837        four
21838        five
21839        "
21840    });
21841    cx.set_state(indoc! {"
21842        one
21843        TWO
21844        ˇTHREE
21845        FOUR
21846        five
21847    "});
21848    cx.run_until_parked();
21849    cx.update_editor(|editor, window, cx| {
21850        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21851    });
21852    cx.run_until_parked();
21853    cx.assert_index_text(Some(indoc! {"
21854        one
21855        TWO
21856        THREE
21857        FOUR
21858        five
21859    "}));
21860    cx.set_state(indoc! { "
21861        one
21862        TWO
21863        ˇTHREE-HUNDRED
21864        FOUR
21865        five
21866    "});
21867    cx.run_until_parked();
21868    cx.update_editor(|editor, window, cx| {
21869        let snapshot = editor.snapshot(window, cx);
21870        let hunks = editor
21871            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21872            .collect::<Vec<_>>();
21873        assert_eq!(hunks.len(), 1);
21874        assert_eq!(
21875            hunks[0].status(),
21876            DiffHunkStatus {
21877                kind: DiffHunkStatusKind::Modified,
21878                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21879            }
21880        );
21881
21882        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21883    });
21884    cx.run_until_parked();
21885    cx.assert_index_text(Some(indoc! {"
21886        one
21887        TWO
21888        THREE-HUNDRED
21889        FOUR
21890        five
21891    "}));
21892}
21893
21894#[gpui::test]
21895fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21896    init_test(cx, |_| {});
21897
21898    let editor = cx.add_window(|window, cx| {
21899        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21900        build_editor(buffer, window, cx)
21901    });
21902
21903    let render_args = Arc::new(Mutex::new(None));
21904    let snapshot = editor
21905        .update(cx, |editor, window, cx| {
21906            let snapshot = editor.buffer().read(cx).snapshot(cx);
21907            let range =
21908                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21909
21910            struct RenderArgs {
21911                row: MultiBufferRow,
21912                folded: bool,
21913                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21914            }
21915
21916            let crease = Crease::inline(
21917                range,
21918                FoldPlaceholder::test(),
21919                {
21920                    let toggle_callback = render_args.clone();
21921                    move |row, folded, callback, _window, _cx| {
21922                        *toggle_callback.lock() = Some(RenderArgs {
21923                            row,
21924                            folded,
21925                            callback,
21926                        });
21927                        div()
21928                    }
21929                },
21930                |_row, _folded, _window, _cx| div(),
21931            );
21932
21933            editor.insert_creases(Some(crease), cx);
21934            let snapshot = editor.snapshot(window, cx);
21935            let _div =
21936                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21937            snapshot
21938        })
21939        .unwrap();
21940
21941    let render_args = render_args.lock().take().unwrap();
21942    assert_eq!(render_args.row, MultiBufferRow(1));
21943    assert!(!render_args.folded);
21944    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21945
21946    cx.update_window(*editor, |_, window, cx| {
21947        (render_args.callback)(true, window, cx)
21948    })
21949    .unwrap();
21950    let snapshot = editor
21951        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21952        .unwrap();
21953    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21954
21955    cx.update_window(*editor, |_, window, cx| {
21956        (render_args.callback)(false, window, cx)
21957    })
21958    .unwrap();
21959    let snapshot = editor
21960        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21961        .unwrap();
21962    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21963}
21964
21965#[gpui::test]
21966async fn test_input_text(cx: &mut TestAppContext) {
21967    init_test(cx, |_| {});
21968    let mut cx = EditorTestContext::new(cx).await;
21969
21970    cx.set_state(
21971        &r#"ˇone
21972        two
21973
21974        three
21975        fourˇ
21976        five
21977
21978        siˇx"#
21979            .unindent(),
21980    );
21981
21982    cx.dispatch_action(HandleInput(String::new()));
21983    cx.assert_editor_state(
21984        &r#"ˇone
21985        two
21986
21987        three
21988        fourˇ
21989        five
21990
21991        siˇx"#
21992            .unindent(),
21993    );
21994
21995    cx.dispatch_action(HandleInput("AAAA".to_string()));
21996    cx.assert_editor_state(
21997        &r#"AAAAˇone
21998        two
21999
22000        three
22001        fourAAAAˇ
22002        five
22003
22004        siAAAAˇx"#
22005            .unindent(),
22006    );
22007}
22008
22009#[gpui::test]
22010async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22011    init_test(cx, |_| {});
22012
22013    let mut cx = EditorTestContext::new(cx).await;
22014    cx.set_state(
22015        r#"let foo = 1;
22016let foo = 2;
22017let foo = 3;
22018let fooˇ = 4;
22019let foo = 5;
22020let foo = 6;
22021let foo = 7;
22022let foo = 8;
22023let foo = 9;
22024let foo = 10;
22025let foo = 11;
22026let foo = 12;
22027let foo = 13;
22028let foo = 14;
22029let foo = 15;"#,
22030    );
22031
22032    cx.update_editor(|e, window, cx| {
22033        assert_eq!(
22034            e.next_scroll_position,
22035            NextScrollCursorCenterTopBottom::Center,
22036            "Default next scroll direction is center",
22037        );
22038
22039        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22040        assert_eq!(
22041            e.next_scroll_position,
22042            NextScrollCursorCenterTopBottom::Top,
22043            "After center, next scroll direction should be top",
22044        );
22045
22046        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22047        assert_eq!(
22048            e.next_scroll_position,
22049            NextScrollCursorCenterTopBottom::Bottom,
22050            "After top, next scroll direction should be bottom",
22051        );
22052
22053        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22054        assert_eq!(
22055            e.next_scroll_position,
22056            NextScrollCursorCenterTopBottom::Center,
22057            "After bottom, scrolling should start over",
22058        );
22059
22060        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22061        assert_eq!(
22062            e.next_scroll_position,
22063            NextScrollCursorCenterTopBottom::Top,
22064            "Scrolling continues if retriggered fast enough"
22065        );
22066    });
22067
22068    cx.executor()
22069        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22070    cx.executor().run_until_parked();
22071    cx.update_editor(|e, _, _| {
22072        assert_eq!(
22073            e.next_scroll_position,
22074            NextScrollCursorCenterTopBottom::Center,
22075            "If scrolling is not triggered fast enough, it should reset"
22076        );
22077    });
22078}
22079
22080#[gpui::test]
22081async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22082    init_test(cx, |_| {});
22083    let mut cx = EditorLspTestContext::new_rust(
22084        lsp::ServerCapabilities {
22085            definition_provider: Some(lsp::OneOf::Left(true)),
22086            references_provider: Some(lsp::OneOf::Left(true)),
22087            ..lsp::ServerCapabilities::default()
22088        },
22089        cx,
22090    )
22091    .await;
22092
22093    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22094        let go_to_definition = cx
22095            .lsp
22096            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22097                move |params, _| async move {
22098                    if empty_go_to_definition {
22099                        Ok(None)
22100                    } else {
22101                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22102                            uri: params.text_document_position_params.text_document.uri,
22103                            range: lsp::Range::new(
22104                                lsp::Position::new(4, 3),
22105                                lsp::Position::new(4, 6),
22106                            ),
22107                        })))
22108                    }
22109                },
22110            );
22111        let references = cx
22112            .lsp
22113            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22114                Ok(Some(vec![lsp::Location {
22115                    uri: params.text_document_position.text_document.uri,
22116                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22117                }]))
22118            });
22119        (go_to_definition, references)
22120    };
22121
22122    cx.set_state(
22123        &r#"fn one() {
22124            let mut a = ˇtwo();
22125        }
22126
22127        fn two() {}"#
22128            .unindent(),
22129    );
22130    set_up_lsp_handlers(false, &mut cx);
22131    let navigated = cx
22132        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22133        .await
22134        .expect("Failed to navigate to definition");
22135    assert_eq!(
22136        navigated,
22137        Navigated::Yes,
22138        "Should have navigated to definition from the GetDefinition response"
22139    );
22140    cx.assert_editor_state(
22141        &r#"fn one() {
22142            let mut a = two();
22143        }
22144
22145        fn «twoˇ»() {}"#
22146            .unindent(),
22147    );
22148
22149    let editors = cx.update_workspace(|workspace, _, cx| {
22150        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22151    });
22152    cx.update_editor(|_, _, test_editor_cx| {
22153        assert_eq!(
22154            editors.len(),
22155            1,
22156            "Initially, only one, test, editor should be open in the workspace"
22157        );
22158        assert_eq!(
22159            test_editor_cx.entity(),
22160            editors.last().expect("Asserted len is 1").clone()
22161        );
22162    });
22163
22164    set_up_lsp_handlers(true, &mut cx);
22165    let navigated = cx
22166        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22167        .await
22168        .expect("Failed to navigate to lookup references");
22169    assert_eq!(
22170        navigated,
22171        Navigated::Yes,
22172        "Should have navigated to references as a fallback after empty GoToDefinition response"
22173    );
22174    // We should not change the selections in the existing file,
22175    // if opening another milti buffer with the references
22176    cx.assert_editor_state(
22177        &r#"fn one() {
22178            let mut a = two();
22179        }
22180
22181        fn «twoˇ»() {}"#
22182            .unindent(),
22183    );
22184    let editors = cx.update_workspace(|workspace, _, cx| {
22185        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22186    });
22187    cx.update_editor(|_, _, test_editor_cx| {
22188        assert_eq!(
22189            editors.len(),
22190            2,
22191            "After falling back to references search, we open a new editor with the results"
22192        );
22193        let references_fallback_text = editors
22194            .into_iter()
22195            .find(|new_editor| *new_editor != test_editor_cx.entity())
22196            .expect("Should have one non-test editor now")
22197            .read(test_editor_cx)
22198            .text(test_editor_cx);
22199        assert_eq!(
22200            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22201            "Should use the range from the references response and not the GoToDefinition one"
22202        );
22203    });
22204}
22205
22206#[gpui::test]
22207async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22208    init_test(cx, |_| {});
22209    cx.update(|cx| {
22210        let mut editor_settings = EditorSettings::get_global(cx).clone();
22211        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22212        EditorSettings::override_global(editor_settings, cx);
22213    });
22214    let mut cx = EditorLspTestContext::new_rust(
22215        lsp::ServerCapabilities {
22216            definition_provider: Some(lsp::OneOf::Left(true)),
22217            references_provider: Some(lsp::OneOf::Left(true)),
22218            ..lsp::ServerCapabilities::default()
22219        },
22220        cx,
22221    )
22222    .await;
22223    let original_state = r#"fn one() {
22224        let mut a = ˇtwo();
22225    }
22226
22227    fn two() {}"#
22228        .unindent();
22229    cx.set_state(&original_state);
22230
22231    let mut go_to_definition = cx
22232        .lsp
22233        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22234            move |_, _| async move { Ok(None) },
22235        );
22236    let _references = cx
22237        .lsp
22238        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22239            panic!("Should not call for references with no go to definition fallback")
22240        });
22241
22242    let navigated = cx
22243        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22244        .await
22245        .expect("Failed to navigate to lookup references");
22246    go_to_definition
22247        .next()
22248        .await
22249        .expect("Should have called the go_to_definition handler");
22250
22251    assert_eq!(
22252        navigated,
22253        Navigated::No,
22254        "Should have navigated to references as a fallback after empty GoToDefinition response"
22255    );
22256    cx.assert_editor_state(&original_state);
22257    let editors = cx.update_workspace(|workspace, _, cx| {
22258        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22259    });
22260    cx.update_editor(|_, _, _| {
22261        assert_eq!(
22262            editors.len(),
22263            1,
22264            "After unsuccessful fallback, no other editor should have been opened"
22265        );
22266    });
22267}
22268
22269#[gpui::test]
22270async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22271    init_test(cx, |_| {});
22272    let mut cx = EditorLspTestContext::new_rust(
22273        lsp::ServerCapabilities {
22274            references_provider: Some(lsp::OneOf::Left(true)),
22275            ..lsp::ServerCapabilities::default()
22276        },
22277        cx,
22278    )
22279    .await;
22280
22281    cx.set_state(
22282        &r#"
22283        fn one() {
22284            let mut a = two();
22285        }
22286
22287        fn ˇtwo() {}"#
22288            .unindent(),
22289    );
22290    cx.lsp
22291        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22292            Ok(Some(vec![
22293                lsp::Location {
22294                    uri: params.text_document_position.text_document.uri.clone(),
22295                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22296                },
22297                lsp::Location {
22298                    uri: params.text_document_position.text_document.uri,
22299                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22300                },
22301            ]))
22302        });
22303    let navigated = cx
22304        .update_editor(|editor, window, cx| {
22305            editor.find_all_references(&FindAllReferences, window, cx)
22306        })
22307        .unwrap()
22308        .await
22309        .expect("Failed to navigate to references");
22310    assert_eq!(
22311        navigated,
22312        Navigated::Yes,
22313        "Should have navigated to references from the FindAllReferences response"
22314    );
22315    cx.assert_editor_state(
22316        &r#"fn one() {
22317            let mut a = two();
22318        }
22319
22320        fn ˇtwo() {}"#
22321            .unindent(),
22322    );
22323
22324    let editors = cx.update_workspace(|workspace, _, cx| {
22325        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22326    });
22327    cx.update_editor(|_, _, _| {
22328        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22329    });
22330
22331    cx.set_state(
22332        &r#"fn one() {
22333            let mut a = ˇtwo();
22334        }
22335
22336        fn two() {}"#
22337            .unindent(),
22338    );
22339    let navigated = cx
22340        .update_editor(|editor, window, cx| {
22341            editor.find_all_references(&FindAllReferences, window, cx)
22342        })
22343        .unwrap()
22344        .await
22345        .expect("Failed to navigate to references");
22346    assert_eq!(
22347        navigated,
22348        Navigated::Yes,
22349        "Should have navigated to references from the FindAllReferences response"
22350    );
22351    cx.assert_editor_state(
22352        &r#"fn one() {
22353            let mut a = ˇtwo();
22354        }
22355
22356        fn two() {}"#
22357            .unindent(),
22358    );
22359    let editors = cx.update_workspace(|workspace, _, cx| {
22360        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22361    });
22362    cx.update_editor(|_, _, _| {
22363        assert_eq!(
22364            editors.len(),
22365            2,
22366            "should have re-used the previous multibuffer"
22367        );
22368    });
22369
22370    cx.set_state(
22371        &r#"fn one() {
22372            let mut a = ˇtwo();
22373        }
22374        fn three() {}
22375        fn two() {}"#
22376            .unindent(),
22377    );
22378    cx.lsp
22379        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22380            Ok(Some(vec![
22381                lsp::Location {
22382                    uri: params.text_document_position.text_document.uri.clone(),
22383                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22384                },
22385                lsp::Location {
22386                    uri: params.text_document_position.text_document.uri,
22387                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22388                },
22389            ]))
22390        });
22391    let navigated = cx
22392        .update_editor(|editor, window, cx| {
22393            editor.find_all_references(&FindAllReferences, window, cx)
22394        })
22395        .unwrap()
22396        .await
22397        .expect("Failed to navigate to references");
22398    assert_eq!(
22399        navigated,
22400        Navigated::Yes,
22401        "Should have navigated to references from the FindAllReferences response"
22402    );
22403    cx.assert_editor_state(
22404        &r#"fn one() {
22405                let mut a = ˇtwo();
22406            }
22407            fn three() {}
22408            fn two() {}"#
22409            .unindent(),
22410    );
22411    let editors = cx.update_workspace(|workspace, _, cx| {
22412        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22413    });
22414    cx.update_editor(|_, _, _| {
22415        assert_eq!(
22416            editors.len(),
22417            3,
22418            "should have used a new multibuffer as offsets changed"
22419        );
22420    });
22421}
22422#[gpui::test]
22423async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22424    init_test(cx, |_| {});
22425
22426    let language = Arc::new(Language::new(
22427        LanguageConfig::default(),
22428        Some(tree_sitter_rust::LANGUAGE.into()),
22429    ));
22430
22431    let text = r#"
22432        #[cfg(test)]
22433        mod tests() {
22434            #[test]
22435            fn runnable_1() {
22436                let a = 1;
22437            }
22438
22439            #[test]
22440            fn runnable_2() {
22441                let a = 1;
22442                let b = 2;
22443            }
22444        }
22445    "#
22446    .unindent();
22447
22448    let fs = FakeFs::new(cx.executor());
22449    fs.insert_file("/file.rs", Default::default()).await;
22450
22451    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22452    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22453    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22454    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22455    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22456
22457    let editor = cx.new_window_entity(|window, cx| {
22458        Editor::new(
22459            EditorMode::full(),
22460            multi_buffer,
22461            Some(project.clone()),
22462            window,
22463            cx,
22464        )
22465    });
22466
22467    editor.update_in(cx, |editor, window, cx| {
22468        let snapshot = editor.buffer().read(cx).snapshot(cx);
22469        editor.tasks.insert(
22470            (buffer.read(cx).remote_id(), 3),
22471            RunnableTasks {
22472                templates: vec![],
22473                offset: snapshot.anchor_before(MultiBufferOffset(43)),
22474                column: 0,
22475                extra_variables: HashMap::default(),
22476                context_range: BufferOffset(43)..BufferOffset(85),
22477            },
22478        );
22479        editor.tasks.insert(
22480            (buffer.read(cx).remote_id(), 8),
22481            RunnableTasks {
22482                templates: vec![],
22483                offset: snapshot.anchor_before(MultiBufferOffset(86)),
22484                column: 0,
22485                extra_variables: HashMap::default(),
22486                context_range: BufferOffset(86)..BufferOffset(191),
22487            },
22488        );
22489
22490        // Test finding task when cursor is inside function body
22491        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22492            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22493        });
22494        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22495        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22496
22497        // Test finding task when cursor is on function name
22498        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22499            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22500        });
22501        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22502        assert_eq!(row, 8, "Should find task when cursor is on function name");
22503    });
22504}
22505
22506#[gpui::test]
22507async fn test_folding_buffers(cx: &mut TestAppContext) {
22508    init_test(cx, |_| {});
22509
22510    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22511    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22512    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22513
22514    let fs = FakeFs::new(cx.executor());
22515    fs.insert_tree(
22516        path!("/a"),
22517        json!({
22518            "first.rs": sample_text_1,
22519            "second.rs": sample_text_2,
22520            "third.rs": sample_text_3,
22521        }),
22522    )
22523    .await;
22524    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22525    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22526    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22527    let worktree = project.update(cx, |project, cx| {
22528        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22529        assert_eq!(worktrees.len(), 1);
22530        worktrees.pop().unwrap()
22531    });
22532    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22533
22534    let buffer_1 = project
22535        .update(cx, |project, cx| {
22536            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22537        })
22538        .await
22539        .unwrap();
22540    let buffer_2 = project
22541        .update(cx, |project, cx| {
22542            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22543        })
22544        .await
22545        .unwrap();
22546    let buffer_3 = project
22547        .update(cx, |project, cx| {
22548            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22549        })
22550        .await
22551        .unwrap();
22552
22553    let multi_buffer = cx.new(|cx| {
22554        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22555        multi_buffer.push_excerpts(
22556            buffer_1.clone(),
22557            [
22558                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22559                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22560                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22561            ],
22562            cx,
22563        );
22564        multi_buffer.push_excerpts(
22565            buffer_2.clone(),
22566            [
22567                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22568                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22569                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22570            ],
22571            cx,
22572        );
22573        multi_buffer.push_excerpts(
22574            buffer_3.clone(),
22575            [
22576                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22577                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22578                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22579            ],
22580            cx,
22581        );
22582        multi_buffer
22583    });
22584    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22585        Editor::new(
22586            EditorMode::full(),
22587            multi_buffer.clone(),
22588            Some(project.clone()),
22589            window,
22590            cx,
22591        )
22592    });
22593
22594    assert_eq!(
22595        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22596        "\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",
22597    );
22598
22599    multi_buffer_editor.update(cx, |editor, cx| {
22600        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22601    });
22602    assert_eq!(
22603        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22604        "\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",
22605        "After folding the first buffer, its text should not be displayed"
22606    );
22607
22608    multi_buffer_editor.update(cx, |editor, cx| {
22609        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22610    });
22611    assert_eq!(
22612        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22613        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22614        "After folding the second buffer, its text should not be displayed"
22615    );
22616
22617    multi_buffer_editor.update(cx, |editor, cx| {
22618        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22619    });
22620    assert_eq!(
22621        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22622        "\n\n\n\n\n",
22623        "After folding the third buffer, its text should not be displayed"
22624    );
22625
22626    // Emulate selection inside the fold logic, that should work
22627    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22628        editor
22629            .snapshot(window, cx)
22630            .next_line_boundary(Point::new(0, 4));
22631    });
22632
22633    multi_buffer_editor.update(cx, |editor, cx| {
22634        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22635    });
22636    assert_eq!(
22637        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22638        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22639        "After unfolding the second buffer, its text should be displayed"
22640    );
22641
22642    // Typing inside of buffer 1 causes that buffer to be unfolded.
22643    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22644        assert_eq!(
22645            multi_buffer
22646                .read(cx)
22647                .snapshot(cx)
22648                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22649                .collect::<String>(),
22650            "bbbb"
22651        );
22652        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22653            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22654        });
22655        editor.handle_input("B", window, cx);
22656    });
22657
22658    assert_eq!(
22659        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22660        "\n\naaaa\nBbbbb\ncccc\n\n\nffff\ngggg\n\n\njjjj\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22661        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22662    );
22663
22664    multi_buffer_editor.update(cx, |editor, cx| {
22665        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22666    });
22667    assert_eq!(
22668        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22669        "\n\naaaa\nBbbbb\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",
22670        "After unfolding the all buffers, all original text should be displayed"
22671    );
22672}
22673
22674#[gpui::test]
22675async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22676    init_test(cx, |_| {});
22677
22678    let sample_text_1 = "1111\n2222\n3333".to_string();
22679    let sample_text_2 = "4444\n5555\n6666".to_string();
22680    let sample_text_3 = "7777\n8888\n9999".to_string();
22681
22682    let fs = FakeFs::new(cx.executor());
22683    fs.insert_tree(
22684        path!("/a"),
22685        json!({
22686            "first.rs": sample_text_1,
22687            "second.rs": sample_text_2,
22688            "third.rs": sample_text_3,
22689        }),
22690    )
22691    .await;
22692    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22693    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22694    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22695    let worktree = project.update(cx, |project, cx| {
22696        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22697        assert_eq!(worktrees.len(), 1);
22698        worktrees.pop().unwrap()
22699    });
22700    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22701
22702    let buffer_1 = project
22703        .update(cx, |project, cx| {
22704            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22705        })
22706        .await
22707        .unwrap();
22708    let buffer_2 = project
22709        .update(cx, |project, cx| {
22710            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22711        })
22712        .await
22713        .unwrap();
22714    let buffer_3 = project
22715        .update(cx, |project, cx| {
22716            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22717        })
22718        .await
22719        .unwrap();
22720
22721    let multi_buffer = cx.new(|cx| {
22722        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22723        multi_buffer.push_excerpts(
22724            buffer_1.clone(),
22725            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22726            cx,
22727        );
22728        multi_buffer.push_excerpts(
22729            buffer_2.clone(),
22730            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22731            cx,
22732        );
22733        multi_buffer.push_excerpts(
22734            buffer_3.clone(),
22735            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22736            cx,
22737        );
22738        multi_buffer
22739    });
22740
22741    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22742        Editor::new(
22743            EditorMode::full(),
22744            multi_buffer,
22745            Some(project.clone()),
22746            window,
22747            cx,
22748        )
22749    });
22750
22751    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22752    assert_eq!(
22753        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22754        full_text,
22755    );
22756
22757    multi_buffer_editor.update(cx, |editor, cx| {
22758        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22759    });
22760    assert_eq!(
22761        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22762        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22763        "After folding the first buffer, its text should not be displayed"
22764    );
22765
22766    multi_buffer_editor.update(cx, |editor, cx| {
22767        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22768    });
22769
22770    assert_eq!(
22771        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22772        "\n\n\n\n\n\n7777\n8888\n9999",
22773        "After folding the second buffer, its text should not be displayed"
22774    );
22775
22776    multi_buffer_editor.update(cx, |editor, cx| {
22777        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22778    });
22779    assert_eq!(
22780        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22781        "\n\n\n\n\n",
22782        "After folding the third buffer, its text should not be displayed"
22783    );
22784
22785    multi_buffer_editor.update(cx, |editor, cx| {
22786        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22787    });
22788    assert_eq!(
22789        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22790        "\n\n\n\n4444\n5555\n6666\n\n",
22791        "After unfolding the second buffer, its text should be displayed"
22792    );
22793
22794    multi_buffer_editor.update(cx, |editor, cx| {
22795        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22796    });
22797    assert_eq!(
22798        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22799        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22800        "After unfolding the first buffer, its text should be displayed"
22801    );
22802
22803    multi_buffer_editor.update(cx, |editor, cx| {
22804        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22805    });
22806    assert_eq!(
22807        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22808        full_text,
22809        "After unfolding all buffers, all original text should be displayed"
22810    );
22811}
22812
22813#[gpui::test]
22814async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22815    init_test(cx, |_| {});
22816
22817    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22818
22819    let fs = FakeFs::new(cx.executor());
22820    fs.insert_tree(
22821        path!("/a"),
22822        json!({
22823            "main.rs": sample_text,
22824        }),
22825    )
22826    .await;
22827    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22828    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22829    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22830    let worktree = project.update(cx, |project, cx| {
22831        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22832        assert_eq!(worktrees.len(), 1);
22833        worktrees.pop().unwrap()
22834    });
22835    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22836
22837    let buffer_1 = project
22838        .update(cx, |project, cx| {
22839            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22840        })
22841        .await
22842        .unwrap();
22843
22844    let multi_buffer = cx.new(|cx| {
22845        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22846        multi_buffer.push_excerpts(
22847            buffer_1.clone(),
22848            [ExcerptRange::new(
22849                Point::new(0, 0)
22850                    ..Point::new(
22851                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22852                        0,
22853                    ),
22854            )],
22855            cx,
22856        );
22857        multi_buffer
22858    });
22859    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22860        Editor::new(
22861            EditorMode::full(),
22862            multi_buffer,
22863            Some(project.clone()),
22864            window,
22865            cx,
22866        )
22867    });
22868
22869    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22870    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22871        enum TestHighlight {}
22872        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22873        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22874        editor.highlight_text::<TestHighlight>(
22875            vec![highlight_range.clone()],
22876            HighlightStyle::color(Hsla::green()),
22877            cx,
22878        );
22879        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22880            s.select_ranges(Some(highlight_range))
22881        });
22882    });
22883
22884    let full_text = format!("\n\n{sample_text}");
22885    assert_eq!(
22886        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22887        full_text,
22888    );
22889}
22890
22891#[gpui::test]
22892async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22893    init_test(cx, |_| {});
22894    cx.update(|cx| {
22895        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22896            "keymaps/default-linux.json",
22897            cx,
22898        )
22899        .unwrap();
22900        cx.bind_keys(default_key_bindings);
22901    });
22902
22903    let (editor, cx) = cx.add_window_view(|window, cx| {
22904        let multi_buffer = MultiBuffer::build_multi(
22905            [
22906                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22907                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22908                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22909                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22910            ],
22911            cx,
22912        );
22913        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22914
22915        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22916        // fold all but the second buffer, so that we test navigating between two
22917        // adjacent folded buffers, as well as folded buffers at the start and
22918        // end the multibuffer
22919        editor.fold_buffer(buffer_ids[0], cx);
22920        editor.fold_buffer(buffer_ids[2], cx);
22921        editor.fold_buffer(buffer_ids[3], cx);
22922
22923        editor
22924    });
22925    cx.simulate_resize(size(px(1000.), px(1000.)));
22926
22927    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22928    cx.assert_excerpts_with_selections(indoc! {"
22929        [EXCERPT]
22930        ˇ[FOLDED]
22931        [EXCERPT]
22932        a1
22933        b1
22934        [EXCERPT]
22935        [FOLDED]
22936        [EXCERPT]
22937        [FOLDED]
22938        "
22939    });
22940    cx.simulate_keystroke("down");
22941    cx.assert_excerpts_with_selections(indoc! {"
22942        [EXCERPT]
22943        [FOLDED]
22944        [EXCERPT]
22945        ˇa1
22946        b1
22947        [EXCERPT]
22948        [FOLDED]
22949        [EXCERPT]
22950        [FOLDED]
22951        "
22952    });
22953    cx.simulate_keystroke("down");
22954    cx.assert_excerpts_with_selections(indoc! {"
22955        [EXCERPT]
22956        [FOLDED]
22957        [EXCERPT]
22958        a1
22959        ˇb1
22960        [EXCERPT]
22961        [FOLDED]
22962        [EXCERPT]
22963        [FOLDED]
22964        "
22965    });
22966    cx.simulate_keystroke("down");
22967    cx.assert_excerpts_with_selections(indoc! {"
22968        [EXCERPT]
22969        [FOLDED]
22970        [EXCERPT]
22971        a1
22972        b1
22973        ˇ[EXCERPT]
22974        [FOLDED]
22975        [EXCERPT]
22976        [FOLDED]
22977        "
22978    });
22979    cx.simulate_keystroke("down");
22980    cx.assert_excerpts_with_selections(indoc! {"
22981        [EXCERPT]
22982        [FOLDED]
22983        [EXCERPT]
22984        a1
22985        b1
22986        [EXCERPT]
22987        ˇ[FOLDED]
22988        [EXCERPT]
22989        [FOLDED]
22990        "
22991    });
22992    for _ in 0..5 {
22993        cx.simulate_keystroke("down");
22994        cx.assert_excerpts_with_selections(indoc! {"
22995            [EXCERPT]
22996            [FOLDED]
22997            [EXCERPT]
22998            a1
22999            b1
23000            [EXCERPT]
23001            [FOLDED]
23002            [EXCERPT]
23003            ˇ[FOLDED]
23004            "
23005        });
23006    }
23007
23008    cx.simulate_keystroke("up");
23009    cx.assert_excerpts_with_selections(indoc! {"
23010        [EXCERPT]
23011        [FOLDED]
23012        [EXCERPT]
23013        a1
23014        b1
23015        [EXCERPT]
23016        ˇ[FOLDED]
23017        [EXCERPT]
23018        [FOLDED]
23019        "
23020    });
23021    cx.simulate_keystroke("up");
23022    cx.assert_excerpts_with_selections(indoc! {"
23023        [EXCERPT]
23024        [FOLDED]
23025        [EXCERPT]
23026        a1
23027        b1
23028        ˇ[EXCERPT]
23029        [FOLDED]
23030        [EXCERPT]
23031        [FOLDED]
23032        "
23033    });
23034    cx.simulate_keystroke("up");
23035    cx.assert_excerpts_with_selections(indoc! {"
23036        [EXCERPT]
23037        [FOLDED]
23038        [EXCERPT]
23039        a1
23040        ˇb1
23041        [EXCERPT]
23042        [FOLDED]
23043        [EXCERPT]
23044        [FOLDED]
23045        "
23046    });
23047    cx.simulate_keystroke("up");
23048    cx.assert_excerpts_with_selections(indoc! {"
23049        [EXCERPT]
23050        [FOLDED]
23051        [EXCERPT]
23052        ˇa1
23053        b1
23054        [EXCERPT]
23055        [FOLDED]
23056        [EXCERPT]
23057        [FOLDED]
23058        "
23059    });
23060    for _ in 0..5 {
23061        cx.simulate_keystroke("up");
23062        cx.assert_excerpts_with_selections(indoc! {"
23063            [EXCERPT]
23064            ˇ[FOLDED]
23065            [EXCERPT]
23066            a1
23067            b1
23068            [EXCERPT]
23069            [FOLDED]
23070            [EXCERPT]
23071            [FOLDED]
23072            "
23073        });
23074    }
23075}
23076
23077#[gpui::test]
23078async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23079    init_test(cx, |_| {});
23080
23081    // Simple insertion
23082    assert_highlighted_edits(
23083        "Hello, world!",
23084        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23085        true,
23086        cx,
23087        |highlighted_edits, cx| {
23088            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23089            assert_eq!(highlighted_edits.highlights.len(), 1);
23090            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23091            assert_eq!(
23092                highlighted_edits.highlights[0].1.background_color,
23093                Some(cx.theme().status().created_background)
23094            );
23095        },
23096    )
23097    .await;
23098
23099    // Replacement
23100    assert_highlighted_edits(
23101        "This is a test.",
23102        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23103        false,
23104        cx,
23105        |highlighted_edits, cx| {
23106            assert_eq!(highlighted_edits.text, "That is a test.");
23107            assert_eq!(highlighted_edits.highlights.len(), 1);
23108            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23109            assert_eq!(
23110                highlighted_edits.highlights[0].1.background_color,
23111                Some(cx.theme().status().created_background)
23112            );
23113        },
23114    )
23115    .await;
23116
23117    // Multiple edits
23118    assert_highlighted_edits(
23119        "Hello, world!",
23120        vec![
23121            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23122            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23123        ],
23124        false,
23125        cx,
23126        |highlighted_edits, cx| {
23127            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23128            assert_eq!(highlighted_edits.highlights.len(), 2);
23129            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23130            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23131            assert_eq!(
23132                highlighted_edits.highlights[0].1.background_color,
23133                Some(cx.theme().status().created_background)
23134            );
23135            assert_eq!(
23136                highlighted_edits.highlights[1].1.background_color,
23137                Some(cx.theme().status().created_background)
23138            );
23139        },
23140    )
23141    .await;
23142
23143    // Multiple lines with edits
23144    assert_highlighted_edits(
23145        "First line\nSecond line\nThird line\nFourth line",
23146        vec![
23147            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23148            (
23149                Point::new(2, 0)..Point::new(2, 10),
23150                "New third line".to_string(),
23151            ),
23152            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23153        ],
23154        false,
23155        cx,
23156        |highlighted_edits, cx| {
23157            assert_eq!(
23158                highlighted_edits.text,
23159                "Second modified\nNew third line\nFourth updated line"
23160            );
23161            assert_eq!(highlighted_edits.highlights.len(), 3);
23162            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23163            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23164            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23165            for highlight in &highlighted_edits.highlights {
23166                assert_eq!(
23167                    highlight.1.background_color,
23168                    Some(cx.theme().status().created_background)
23169                );
23170            }
23171        },
23172    )
23173    .await;
23174}
23175
23176#[gpui::test]
23177async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23178    init_test(cx, |_| {});
23179
23180    // Deletion
23181    assert_highlighted_edits(
23182        "Hello, world!",
23183        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23184        true,
23185        cx,
23186        |highlighted_edits, cx| {
23187            assert_eq!(highlighted_edits.text, "Hello, world!");
23188            assert_eq!(highlighted_edits.highlights.len(), 1);
23189            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23190            assert_eq!(
23191                highlighted_edits.highlights[0].1.background_color,
23192                Some(cx.theme().status().deleted_background)
23193            );
23194        },
23195    )
23196    .await;
23197
23198    // Insertion
23199    assert_highlighted_edits(
23200        "Hello, world!",
23201        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23202        true,
23203        cx,
23204        |highlighted_edits, cx| {
23205            assert_eq!(highlighted_edits.highlights.len(), 1);
23206            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23207            assert_eq!(
23208                highlighted_edits.highlights[0].1.background_color,
23209                Some(cx.theme().status().created_background)
23210            );
23211        },
23212    )
23213    .await;
23214}
23215
23216async fn assert_highlighted_edits(
23217    text: &str,
23218    edits: Vec<(Range<Point>, String)>,
23219    include_deletions: bool,
23220    cx: &mut TestAppContext,
23221    assertion_fn: impl Fn(HighlightedText, &App),
23222) {
23223    let window = cx.add_window(|window, cx| {
23224        let buffer = MultiBuffer::build_simple(text, cx);
23225        Editor::new(EditorMode::full(), buffer, None, window, cx)
23226    });
23227    let cx = &mut VisualTestContext::from_window(*window, cx);
23228
23229    let (buffer, snapshot) = window
23230        .update(cx, |editor, _window, cx| {
23231            (
23232                editor.buffer().clone(),
23233                editor.buffer().read(cx).snapshot(cx),
23234            )
23235        })
23236        .unwrap();
23237
23238    let edits = edits
23239        .into_iter()
23240        .map(|(range, edit)| {
23241            (
23242                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23243                edit,
23244            )
23245        })
23246        .collect::<Vec<_>>();
23247
23248    let text_anchor_edits = edits
23249        .clone()
23250        .into_iter()
23251        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23252        .collect::<Vec<_>>();
23253
23254    let edit_preview = window
23255        .update(cx, |_, _window, cx| {
23256            buffer
23257                .read(cx)
23258                .as_singleton()
23259                .unwrap()
23260                .read(cx)
23261                .preview_edits(text_anchor_edits.into(), cx)
23262        })
23263        .unwrap()
23264        .await;
23265
23266    cx.update(|_window, cx| {
23267        let highlighted_edits = edit_prediction_edit_text(
23268            snapshot.as_singleton().unwrap().2,
23269            &edits,
23270            &edit_preview,
23271            include_deletions,
23272            cx,
23273        );
23274        assertion_fn(highlighted_edits, cx)
23275    });
23276}
23277
23278#[track_caller]
23279fn assert_breakpoint(
23280    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23281    path: &Arc<Path>,
23282    expected: Vec<(u32, Breakpoint)>,
23283) {
23284    if expected.is_empty() {
23285        assert!(!breakpoints.contains_key(path), "{}", path.display());
23286    } else {
23287        let mut breakpoint = breakpoints
23288            .get(path)
23289            .unwrap()
23290            .iter()
23291            .map(|breakpoint| {
23292                (
23293                    breakpoint.row,
23294                    Breakpoint {
23295                        message: breakpoint.message.clone(),
23296                        state: breakpoint.state,
23297                        condition: breakpoint.condition.clone(),
23298                        hit_condition: breakpoint.hit_condition.clone(),
23299                    },
23300                )
23301            })
23302            .collect::<Vec<_>>();
23303
23304        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23305
23306        assert_eq!(expected, breakpoint);
23307    }
23308}
23309
23310fn add_log_breakpoint_at_cursor(
23311    editor: &mut Editor,
23312    log_message: &str,
23313    window: &mut Window,
23314    cx: &mut Context<Editor>,
23315) {
23316    let (anchor, bp) = editor
23317        .breakpoints_at_cursors(window, cx)
23318        .first()
23319        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23320        .unwrap_or_else(|| {
23321            let snapshot = editor.snapshot(window, cx);
23322            let cursor_position: Point =
23323                editor.selections.newest(&snapshot.display_snapshot).head();
23324
23325            let breakpoint_position = snapshot
23326                .buffer_snapshot()
23327                .anchor_before(Point::new(cursor_position.row, 0));
23328
23329            (breakpoint_position, Breakpoint::new_log(log_message))
23330        });
23331
23332    editor.edit_breakpoint_at_anchor(
23333        anchor,
23334        bp,
23335        BreakpointEditAction::EditLogMessage(log_message.into()),
23336        cx,
23337    );
23338}
23339
23340#[gpui::test]
23341async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23342    init_test(cx, |_| {});
23343
23344    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23345    let fs = FakeFs::new(cx.executor());
23346    fs.insert_tree(
23347        path!("/a"),
23348        json!({
23349            "main.rs": sample_text,
23350        }),
23351    )
23352    .await;
23353    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23354    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23355    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23356
23357    let fs = FakeFs::new(cx.executor());
23358    fs.insert_tree(
23359        path!("/a"),
23360        json!({
23361            "main.rs": sample_text,
23362        }),
23363    )
23364    .await;
23365    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23366    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23367    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23368    let worktree_id = workspace
23369        .update(cx, |workspace, _window, cx| {
23370            workspace.project().update(cx, |project, cx| {
23371                project.worktrees(cx).next().unwrap().read(cx).id()
23372            })
23373        })
23374        .unwrap();
23375
23376    let buffer = project
23377        .update(cx, |project, cx| {
23378            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23379        })
23380        .await
23381        .unwrap();
23382
23383    let (editor, cx) = cx.add_window_view(|window, cx| {
23384        Editor::new(
23385            EditorMode::full(),
23386            MultiBuffer::build_from_buffer(buffer, cx),
23387            Some(project.clone()),
23388            window,
23389            cx,
23390        )
23391    });
23392
23393    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23394    let abs_path = project.read_with(cx, |project, cx| {
23395        project
23396            .absolute_path(&project_path, cx)
23397            .map(Arc::from)
23398            .unwrap()
23399    });
23400
23401    // assert we can add breakpoint on the first line
23402    editor.update_in(cx, |editor, window, cx| {
23403        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23404        editor.move_to_end(&MoveToEnd, window, cx);
23405        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23406    });
23407
23408    let breakpoints = editor.update(cx, |editor, cx| {
23409        editor
23410            .breakpoint_store()
23411            .as_ref()
23412            .unwrap()
23413            .read(cx)
23414            .all_source_breakpoints(cx)
23415    });
23416
23417    assert_eq!(1, breakpoints.len());
23418    assert_breakpoint(
23419        &breakpoints,
23420        &abs_path,
23421        vec![
23422            (0, Breakpoint::new_standard()),
23423            (3, Breakpoint::new_standard()),
23424        ],
23425    );
23426
23427    editor.update_in(cx, |editor, window, cx| {
23428        editor.move_to_beginning(&MoveToBeginning, window, cx);
23429        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23430    });
23431
23432    let breakpoints = editor.update(cx, |editor, cx| {
23433        editor
23434            .breakpoint_store()
23435            .as_ref()
23436            .unwrap()
23437            .read(cx)
23438            .all_source_breakpoints(cx)
23439    });
23440
23441    assert_eq!(1, breakpoints.len());
23442    assert_breakpoint(
23443        &breakpoints,
23444        &abs_path,
23445        vec![(3, Breakpoint::new_standard())],
23446    );
23447
23448    editor.update_in(cx, |editor, window, cx| {
23449        editor.move_to_end(&MoveToEnd, window, cx);
23450        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23451    });
23452
23453    let breakpoints = editor.update(cx, |editor, cx| {
23454        editor
23455            .breakpoint_store()
23456            .as_ref()
23457            .unwrap()
23458            .read(cx)
23459            .all_source_breakpoints(cx)
23460    });
23461
23462    assert_eq!(0, breakpoints.len());
23463    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23464}
23465
23466#[gpui::test]
23467async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23468    init_test(cx, |_| {});
23469
23470    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23471
23472    let fs = FakeFs::new(cx.executor());
23473    fs.insert_tree(
23474        path!("/a"),
23475        json!({
23476            "main.rs": sample_text,
23477        }),
23478    )
23479    .await;
23480    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23481    let (workspace, cx) =
23482        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23483
23484    let worktree_id = workspace.update(cx, |workspace, cx| {
23485        workspace.project().update(cx, |project, cx| {
23486            project.worktrees(cx).next().unwrap().read(cx).id()
23487        })
23488    });
23489
23490    let buffer = project
23491        .update(cx, |project, cx| {
23492            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23493        })
23494        .await
23495        .unwrap();
23496
23497    let (editor, cx) = cx.add_window_view(|window, cx| {
23498        Editor::new(
23499            EditorMode::full(),
23500            MultiBuffer::build_from_buffer(buffer, cx),
23501            Some(project.clone()),
23502            window,
23503            cx,
23504        )
23505    });
23506
23507    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23508    let abs_path = project.read_with(cx, |project, cx| {
23509        project
23510            .absolute_path(&project_path, cx)
23511            .map(Arc::from)
23512            .unwrap()
23513    });
23514
23515    editor.update_in(cx, |editor, window, cx| {
23516        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23517    });
23518
23519    let breakpoints = editor.update(cx, |editor, cx| {
23520        editor
23521            .breakpoint_store()
23522            .as_ref()
23523            .unwrap()
23524            .read(cx)
23525            .all_source_breakpoints(cx)
23526    });
23527
23528    assert_breakpoint(
23529        &breakpoints,
23530        &abs_path,
23531        vec![(0, Breakpoint::new_log("hello world"))],
23532    );
23533
23534    // Removing a log message from a log breakpoint should remove it
23535    editor.update_in(cx, |editor, window, cx| {
23536        add_log_breakpoint_at_cursor(editor, "", window, cx);
23537    });
23538
23539    let breakpoints = editor.update(cx, |editor, cx| {
23540        editor
23541            .breakpoint_store()
23542            .as_ref()
23543            .unwrap()
23544            .read(cx)
23545            .all_source_breakpoints(cx)
23546    });
23547
23548    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23549
23550    editor.update_in(cx, |editor, window, cx| {
23551        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23552        editor.move_to_end(&MoveToEnd, window, cx);
23553        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23554        // Not adding a log message to a standard breakpoint shouldn't remove it
23555        add_log_breakpoint_at_cursor(editor, "", window, cx);
23556    });
23557
23558    let breakpoints = editor.update(cx, |editor, cx| {
23559        editor
23560            .breakpoint_store()
23561            .as_ref()
23562            .unwrap()
23563            .read(cx)
23564            .all_source_breakpoints(cx)
23565    });
23566
23567    assert_breakpoint(
23568        &breakpoints,
23569        &abs_path,
23570        vec![
23571            (0, Breakpoint::new_standard()),
23572            (3, Breakpoint::new_standard()),
23573        ],
23574    );
23575
23576    editor.update_in(cx, |editor, window, cx| {
23577        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23578    });
23579
23580    let breakpoints = editor.update(cx, |editor, cx| {
23581        editor
23582            .breakpoint_store()
23583            .as_ref()
23584            .unwrap()
23585            .read(cx)
23586            .all_source_breakpoints(cx)
23587    });
23588
23589    assert_breakpoint(
23590        &breakpoints,
23591        &abs_path,
23592        vec![
23593            (0, Breakpoint::new_standard()),
23594            (3, Breakpoint::new_log("hello world")),
23595        ],
23596    );
23597
23598    editor.update_in(cx, |editor, window, cx| {
23599        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23600    });
23601
23602    let breakpoints = editor.update(cx, |editor, cx| {
23603        editor
23604            .breakpoint_store()
23605            .as_ref()
23606            .unwrap()
23607            .read(cx)
23608            .all_source_breakpoints(cx)
23609    });
23610
23611    assert_breakpoint(
23612        &breakpoints,
23613        &abs_path,
23614        vec![
23615            (0, Breakpoint::new_standard()),
23616            (3, Breakpoint::new_log("hello Earth!!")),
23617        ],
23618    );
23619}
23620
23621/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23622/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23623/// or when breakpoints were placed out of order. This tests for a regression too
23624#[gpui::test]
23625async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23626    init_test(cx, |_| {});
23627
23628    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23629    let fs = FakeFs::new(cx.executor());
23630    fs.insert_tree(
23631        path!("/a"),
23632        json!({
23633            "main.rs": sample_text,
23634        }),
23635    )
23636    .await;
23637    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23638    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23639    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23640
23641    let fs = FakeFs::new(cx.executor());
23642    fs.insert_tree(
23643        path!("/a"),
23644        json!({
23645            "main.rs": sample_text,
23646        }),
23647    )
23648    .await;
23649    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23650    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23651    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23652    let worktree_id = workspace
23653        .update(cx, |workspace, _window, cx| {
23654            workspace.project().update(cx, |project, cx| {
23655                project.worktrees(cx).next().unwrap().read(cx).id()
23656            })
23657        })
23658        .unwrap();
23659
23660    let buffer = project
23661        .update(cx, |project, cx| {
23662            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23663        })
23664        .await
23665        .unwrap();
23666
23667    let (editor, cx) = cx.add_window_view(|window, cx| {
23668        Editor::new(
23669            EditorMode::full(),
23670            MultiBuffer::build_from_buffer(buffer, cx),
23671            Some(project.clone()),
23672            window,
23673            cx,
23674        )
23675    });
23676
23677    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23678    let abs_path = project.read_with(cx, |project, cx| {
23679        project
23680            .absolute_path(&project_path, cx)
23681            .map(Arc::from)
23682            .unwrap()
23683    });
23684
23685    // assert we can add breakpoint on the first line
23686    editor.update_in(cx, |editor, window, cx| {
23687        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23688        editor.move_to_end(&MoveToEnd, window, cx);
23689        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23690        editor.move_up(&MoveUp, window, cx);
23691        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23692    });
23693
23694    let breakpoints = editor.update(cx, |editor, cx| {
23695        editor
23696            .breakpoint_store()
23697            .as_ref()
23698            .unwrap()
23699            .read(cx)
23700            .all_source_breakpoints(cx)
23701    });
23702
23703    assert_eq!(1, breakpoints.len());
23704    assert_breakpoint(
23705        &breakpoints,
23706        &abs_path,
23707        vec![
23708            (0, Breakpoint::new_standard()),
23709            (2, Breakpoint::new_standard()),
23710            (3, Breakpoint::new_standard()),
23711        ],
23712    );
23713
23714    editor.update_in(cx, |editor, window, cx| {
23715        editor.move_to_beginning(&MoveToBeginning, window, cx);
23716        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23717        editor.move_to_end(&MoveToEnd, window, cx);
23718        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23719        // Disabling a breakpoint that doesn't exist should do nothing
23720        editor.move_up(&MoveUp, window, cx);
23721        editor.move_up(&MoveUp, window, cx);
23722        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23723    });
23724
23725    let breakpoints = editor.update(cx, |editor, cx| {
23726        editor
23727            .breakpoint_store()
23728            .as_ref()
23729            .unwrap()
23730            .read(cx)
23731            .all_source_breakpoints(cx)
23732    });
23733
23734    let disable_breakpoint = {
23735        let mut bp = Breakpoint::new_standard();
23736        bp.state = BreakpointState::Disabled;
23737        bp
23738    };
23739
23740    assert_eq!(1, breakpoints.len());
23741    assert_breakpoint(
23742        &breakpoints,
23743        &abs_path,
23744        vec![
23745            (0, disable_breakpoint.clone()),
23746            (2, Breakpoint::new_standard()),
23747            (3, disable_breakpoint.clone()),
23748        ],
23749    );
23750
23751    editor.update_in(cx, |editor, window, cx| {
23752        editor.move_to_beginning(&MoveToBeginning, window, cx);
23753        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23754        editor.move_to_end(&MoveToEnd, window, cx);
23755        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23756        editor.move_up(&MoveUp, window, cx);
23757        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23758    });
23759
23760    let breakpoints = editor.update(cx, |editor, cx| {
23761        editor
23762            .breakpoint_store()
23763            .as_ref()
23764            .unwrap()
23765            .read(cx)
23766            .all_source_breakpoints(cx)
23767    });
23768
23769    assert_eq!(1, breakpoints.len());
23770    assert_breakpoint(
23771        &breakpoints,
23772        &abs_path,
23773        vec![
23774            (0, Breakpoint::new_standard()),
23775            (2, disable_breakpoint),
23776            (3, Breakpoint::new_standard()),
23777        ],
23778    );
23779}
23780
23781#[gpui::test]
23782async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23783    init_test(cx, |_| {});
23784    let capabilities = lsp::ServerCapabilities {
23785        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23786            prepare_provider: Some(true),
23787            work_done_progress_options: Default::default(),
23788        })),
23789        ..Default::default()
23790    };
23791    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23792
23793    cx.set_state(indoc! {"
23794        struct Fˇoo {}
23795    "});
23796
23797    cx.update_editor(|editor, _, cx| {
23798        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23799        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23800        editor.highlight_background::<DocumentHighlightRead>(
23801            &[highlight_range],
23802            |theme| theme.colors().editor_document_highlight_read_background,
23803            cx,
23804        );
23805    });
23806
23807    let mut prepare_rename_handler = cx
23808        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23809            move |_, _, _| async move {
23810                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23811                    start: lsp::Position {
23812                        line: 0,
23813                        character: 7,
23814                    },
23815                    end: lsp::Position {
23816                        line: 0,
23817                        character: 10,
23818                    },
23819                })))
23820            },
23821        );
23822    let prepare_rename_task = cx
23823        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23824        .expect("Prepare rename was not started");
23825    prepare_rename_handler.next().await.unwrap();
23826    prepare_rename_task.await.expect("Prepare rename failed");
23827
23828    let mut rename_handler =
23829        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23830            let edit = lsp::TextEdit {
23831                range: lsp::Range {
23832                    start: lsp::Position {
23833                        line: 0,
23834                        character: 7,
23835                    },
23836                    end: lsp::Position {
23837                        line: 0,
23838                        character: 10,
23839                    },
23840                },
23841                new_text: "FooRenamed".to_string(),
23842            };
23843            Ok(Some(lsp::WorkspaceEdit::new(
23844                // Specify the same edit twice
23845                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23846            )))
23847        });
23848    let rename_task = cx
23849        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23850        .expect("Confirm rename was not started");
23851    rename_handler.next().await.unwrap();
23852    rename_task.await.expect("Confirm rename failed");
23853    cx.run_until_parked();
23854
23855    // Despite two edits, only one is actually applied as those are identical
23856    cx.assert_editor_state(indoc! {"
23857        struct FooRenamedˇ {}
23858    "});
23859}
23860
23861#[gpui::test]
23862async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23863    init_test(cx, |_| {});
23864    // These capabilities indicate that the server does not support prepare rename.
23865    let capabilities = lsp::ServerCapabilities {
23866        rename_provider: Some(lsp::OneOf::Left(true)),
23867        ..Default::default()
23868    };
23869    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23870
23871    cx.set_state(indoc! {"
23872        struct Fˇoo {}
23873    "});
23874
23875    cx.update_editor(|editor, _window, cx| {
23876        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23877        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23878        editor.highlight_background::<DocumentHighlightRead>(
23879            &[highlight_range],
23880            |theme| theme.colors().editor_document_highlight_read_background,
23881            cx,
23882        );
23883    });
23884
23885    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23886        .expect("Prepare rename was not started")
23887        .await
23888        .expect("Prepare rename failed");
23889
23890    let mut rename_handler =
23891        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23892            let edit = lsp::TextEdit {
23893                range: lsp::Range {
23894                    start: lsp::Position {
23895                        line: 0,
23896                        character: 7,
23897                    },
23898                    end: lsp::Position {
23899                        line: 0,
23900                        character: 10,
23901                    },
23902                },
23903                new_text: "FooRenamed".to_string(),
23904            };
23905            Ok(Some(lsp::WorkspaceEdit::new(
23906                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23907            )))
23908        });
23909    let rename_task = cx
23910        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23911        .expect("Confirm rename was not started");
23912    rename_handler.next().await.unwrap();
23913    rename_task.await.expect("Confirm rename failed");
23914    cx.run_until_parked();
23915
23916    // Correct range is renamed, as `surrounding_word` is used to find it.
23917    cx.assert_editor_state(indoc! {"
23918        struct FooRenamedˇ {}
23919    "});
23920}
23921
23922#[gpui::test]
23923async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23924    init_test(cx, |_| {});
23925    let mut cx = EditorTestContext::new(cx).await;
23926
23927    let language = Arc::new(
23928        Language::new(
23929            LanguageConfig::default(),
23930            Some(tree_sitter_html::LANGUAGE.into()),
23931        )
23932        .with_brackets_query(
23933            r#"
23934            ("<" @open "/>" @close)
23935            ("</" @open ">" @close)
23936            ("<" @open ">" @close)
23937            ("\"" @open "\"" @close)
23938            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23939        "#,
23940        )
23941        .unwrap(),
23942    );
23943    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
23944
23945    cx.set_state(indoc! {"
23946        <span>ˇ</span>
23947    "});
23948    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23949    cx.assert_editor_state(indoc! {"
23950        <span>
23951        ˇ
23952        </span>
23953    "});
23954
23955    cx.set_state(indoc! {"
23956        <span><span></span>ˇ</span>
23957    "});
23958    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23959    cx.assert_editor_state(indoc! {"
23960        <span><span></span>
23961        ˇ</span>
23962    "});
23963
23964    cx.set_state(indoc! {"
23965        <span>ˇ
23966        </span>
23967    "});
23968    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23969    cx.assert_editor_state(indoc! {"
23970        <span>
23971        ˇ
23972        </span>
23973    "});
23974}
23975
23976#[gpui::test(iterations = 10)]
23977async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23978    init_test(cx, |_| {});
23979
23980    let fs = FakeFs::new(cx.executor());
23981    fs.insert_tree(
23982        path!("/dir"),
23983        json!({
23984            "a.ts": "a",
23985        }),
23986    )
23987    .await;
23988
23989    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23990    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23991    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23992
23993    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23994    language_registry.add(Arc::new(Language::new(
23995        LanguageConfig {
23996            name: "TypeScript".into(),
23997            matcher: LanguageMatcher {
23998                path_suffixes: vec!["ts".to_string()],
23999                ..Default::default()
24000            },
24001            ..Default::default()
24002        },
24003        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24004    )));
24005    let mut fake_language_servers = language_registry.register_fake_lsp(
24006        "TypeScript",
24007        FakeLspAdapter {
24008            capabilities: lsp::ServerCapabilities {
24009                code_lens_provider: Some(lsp::CodeLensOptions {
24010                    resolve_provider: Some(true),
24011                }),
24012                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24013                    commands: vec!["_the/command".to_string()],
24014                    ..lsp::ExecuteCommandOptions::default()
24015                }),
24016                ..lsp::ServerCapabilities::default()
24017            },
24018            ..FakeLspAdapter::default()
24019        },
24020    );
24021
24022    let editor = workspace
24023        .update(cx, |workspace, window, cx| {
24024            workspace.open_abs_path(
24025                PathBuf::from(path!("/dir/a.ts")),
24026                OpenOptions::default(),
24027                window,
24028                cx,
24029            )
24030        })
24031        .unwrap()
24032        .await
24033        .unwrap()
24034        .downcast::<Editor>()
24035        .unwrap();
24036    cx.executor().run_until_parked();
24037
24038    let fake_server = fake_language_servers.next().await.unwrap();
24039
24040    let buffer = editor.update(cx, |editor, cx| {
24041        editor
24042            .buffer()
24043            .read(cx)
24044            .as_singleton()
24045            .expect("have opened a single file by path")
24046    });
24047
24048    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24049    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24050    drop(buffer_snapshot);
24051    let actions = cx
24052        .update_window(*workspace, |_, window, cx| {
24053            project.code_actions(&buffer, anchor..anchor, window, cx)
24054        })
24055        .unwrap();
24056
24057    fake_server
24058        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24059            Ok(Some(vec![
24060                lsp::CodeLens {
24061                    range: lsp::Range::default(),
24062                    command: Some(lsp::Command {
24063                        title: "Code lens command".to_owned(),
24064                        command: "_the/command".to_owned(),
24065                        arguments: None,
24066                    }),
24067                    data: None,
24068                },
24069                lsp::CodeLens {
24070                    range: lsp::Range::default(),
24071                    command: Some(lsp::Command {
24072                        title: "Command not in capabilities".to_owned(),
24073                        command: "not in capabilities".to_owned(),
24074                        arguments: None,
24075                    }),
24076                    data: None,
24077                },
24078                lsp::CodeLens {
24079                    range: lsp::Range {
24080                        start: lsp::Position {
24081                            line: 1,
24082                            character: 1,
24083                        },
24084                        end: lsp::Position {
24085                            line: 1,
24086                            character: 1,
24087                        },
24088                    },
24089                    command: Some(lsp::Command {
24090                        title: "Command not in range".to_owned(),
24091                        command: "_the/command".to_owned(),
24092                        arguments: None,
24093                    }),
24094                    data: None,
24095                },
24096            ]))
24097        })
24098        .next()
24099        .await;
24100
24101    let actions = actions.await.unwrap();
24102    assert_eq!(
24103        actions.len(),
24104        1,
24105        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24106    );
24107    let action = actions[0].clone();
24108    let apply = project.update(cx, |project, cx| {
24109        project.apply_code_action(buffer.clone(), action, true, cx)
24110    });
24111
24112    // Resolving the code action does not populate its edits. In absence of
24113    // edits, we must execute the given command.
24114    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24115        |mut lens, _| async move {
24116            let lens_command = lens.command.as_mut().expect("should have a command");
24117            assert_eq!(lens_command.title, "Code lens command");
24118            lens_command.arguments = Some(vec![json!("the-argument")]);
24119            Ok(lens)
24120        },
24121    );
24122
24123    // While executing the command, the language server sends the editor
24124    // a `workspaceEdit` request.
24125    fake_server
24126        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24127            let fake = fake_server.clone();
24128            move |params, _| {
24129                assert_eq!(params.command, "_the/command");
24130                let fake = fake.clone();
24131                async move {
24132                    fake.server
24133                        .request::<lsp::request::ApplyWorkspaceEdit>(
24134                            lsp::ApplyWorkspaceEditParams {
24135                                label: None,
24136                                edit: lsp::WorkspaceEdit {
24137                                    changes: Some(
24138                                        [(
24139                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24140                                            vec![lsp::TextEdit {
24141                                                range: lsp::Range::new(
24142                                                    lsp::Position::new(0, 0),
24143                                                    lsp::Position::new(0, 0),
24144                                                ),
24145                                                new_text: "X".into(),
24146                                            }],
24147                                        )]
24148                                        .into_iter()
24149                                        .collect(),
24150                                    ),
24151                                    ..lsp::WorkspaceEdit::default()
24152                                },
24153                            },
24154                        )
24155                        .await
24156                        .into_response()
24157                        .unwrap();
24158                    Ok(Some(json!(null)))
24159                }
24160            }
24161        })
24162        .next()
24163        .await;
24164
24165    // Applying the code lens command returns a project transaction containing the edits
24166    // sent by the language server in its `workspaceEdit` request.
24167    let transaction = apply.await.unwrap();
24168    assert!(transaction.0.contains_key(&buffer));
24169    buffer.update(cx, |buffer, cx| {
24170        assert_eq!(buffer.text(), "Xa");
24171        buffer.undo(cx);
24172        assert_eq!(buffer.text(), "a");
24173    });
24174
24175    let actions_after_edits = cx
24176        .update_window(*workspace, |_, window, cx| {
24177            project.code_actions(&buffer, anchor..anchor, window, cx)
24178        })
24179        .unwrap()
24180        .await
24181        .unwrap();
24182    assert_eq!(
24183        actions, actions_after_edits,
24184        "For the same selection, same code lens actions should be returned"
24185    );
24186
24187    let _responses =
24188        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24189            panic!("No more code lens requests are expected");
24190        });
24191    editor.update_in(cx, |editor, window, cx| {
24192        editor.select_all(&SelectAll, window, cx);
24193    });
24194    cx.executor().run_until_parked();
24195    let new_actions = cx
24196        .update_window(*workspace, |_, window, cx| {
24197            project.code_actions(&buffer, anchor..anchor, window, cx)
24198        })
24199        .unwrap()
24200        .await
24201        .unwrap();
24202    assert_eq!(
24203        actions, new_actions,
24204        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24205    );
24206}
24207
24208#[gpui::test]
24209async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24210    init_test(cx, |_| {});
24211
24212    let fs = FakeFs::new(cx.executor());
24213    let main_text = r#"fn main() {
24214println!("1");
24215println!("2");
24216println!("3");
24217println!("4");
24218println!("5");
24219}"#;
24220    let lib_text = "mod foo {}";
24221    fs.insert_tree(
24222        path!("/a"),
24223        json!({
24224            "lib.rs": lib_text,
24225            "main.rs": main_text,
24226        }),
24227    )
24228    .await;
24229
24230    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24231    let (workspace, cx) =
24232        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24233    let worktree_id = workspace.update(cx, |workspace, cx| {
24234        workspace.project().update(cx, |project, cx| {
24235            project.worktrees(cx).next().unwrap().read(cx).id()
24236        })
24237    });
24238
24239    let expected_ranges = vec![
24240        Point::new(0, 0)..Point::new(0, 0),
24241        Point::new(1, 0)..Point::new(1, 1),
24242        Point::new(2, 0)..Point::new(2, 2),
24243        Point::new(3, 0)..Point::new(3, 3),
24244    ];
24245
24246    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24247    let editor_1 = workspace
24248        .update_in(cx, |workspace, window, cx| {
24249            workspace.open_path(
24250                (worktree_id, rel_path("main.rs")),
24251                Some(pane_1.downgrade()),
24252                true,
24253                window,
24254                cx,
24255            )
24256        })
24257        .unwrap()
24258        .await
24259        .downcast::<Editor>()
24260        .unwrap();
24261    pane_1.update(cx, |pane, cx| {
24262        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24263        open_editor.update(cx, |editor, cx| {
24264            assert_eq!(
24265                editor.display_text(cx),
24266                main_text,
24267                "Original main.rs text on initial open",
24268            );
24269            assert_eq!(
24270                editor
24271                    .selections
24272                    .all::<Point>(&editor.display_snapshot(cx))
24273                    .into_iter()
24274                    .map(|s| s.range())
24275                    .collect::<Vec<_>>(),
24276                vec![Point::zero()..Point::zero()],
24277                "Default selections on initial open",
24278            );
24279        })
24280    });
24281    editor_1.update_in(cx, |editor, window, cx| {
24282        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24283            s.select_ranges(expected_ranges.clone());
24284        });
24285    });
24286
24287    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24288        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24289    });
24290    let editor_2 = workspace
24291        .update_in(cx, |workspace, window, cx| {
24292            workspace.open_path(
24293                (worktree_id, rel_path("main.rs")),
24294                Some(pane_2.downgrade()),
24295                true,
24296                window,
24297                cx,
24298            )
24299        })
24300        .unwrap()
24301        .await
24302        .downcast::<Editor>()
24303        .unwrap();
24304    pane_2.update(cx, |pane, cx| {
24305        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24306        open_editor.update(cx, |editor, cx| {
24307            assert_eq!(
24308                editor.display_text(cx),
24309                main_text,
24310                "Original main.rs text on initial open in another panel",
24311            );
24312            assert_eq!(
24313                editor
24314                    .selections
24315                    .all::<Point>(&editor.display_snapshot(cx))
24316                    .into_iter()
24317                    .map(|s| s.range())
24318                    .collect::<Vec<_>>(),
24319                vec![Point::zero()..Point::zero()],
24320                "Default selections on initial open in another panel",
24321            );
24322        })
24323    });
24324
24325    editor_2.update_in(cx, |editor, window, cx| {
24326        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24327    });
24328
24329    let _other_editor_1 = workspace
24330        .update_in(cx, |workspace, window, cx| {
24331            workspace.open_path(
24332                (worktree_id, rel_path("lib.rs")),
24333                Some(pane_1.downgrade()),
24334                true,
24335                window,
24336                cx,
24337            )
24338        })
24339        .unwrap()
24340        .await
24341        .downcast::<Editor>()
24342        .unwrap();
24343    pane_1
24344        .update_in(cx, |pane, window, cx| {
24345            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24346        })
24347        .await
24348        .unwrap();
24349    drop(editor_1);
24350    pane_1.update(cx, |pane, cx| {
24351        pane.active_item()
24352            .unwrap()
24353            .downcast::<Editor>()
24354            .unwrap()
24355            .update(cx, |editor, cx| {
24356                assert_eq!(
24357                    editor.display_text(cx),
24358                    lib_text,
24359                    "Other file should be open and active",
24360                );
24361            });
24362        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24363    });
24364
24365    let _other_editor_2 = workspace
24366        .update_in(cx, |workspace, window, cx| {
24367            workspace.open_path(
24368                (worktree_id, rel_path("lib.rs")),
24369                Some(pane_2.downgrade()),
24370                true,
24371                window,
24372                cx,
24373            )
24374        })
24375        .unwrap()
24376        .await
24377        .downcast::<Editor>()
24378        .unwrap();
24379    pane_2
24380        .update_in(cx, |pane, window, cx| {
24381            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24382        })
24383        .await
24384        .unwrap();
24385    drop(editor_2);
24386    pane_2.update(cx, |pane, cx| {
24387        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24388        open_editor.update(cx, |editor, cx| {
24389            assert_eq!(
24390                editor.display_text(cx),
24391                lib_text,
24392                "Other file should be open and active in another panel too",
24393            );
24394        });
24395        assert_eq!(
24396            pane.items().count(),
24397            1,
24398            "No other editors should be open in another pane",
24399        );
24400    });
24401
24402    let _editor_1_reopened = workspace
24403        .update_in(cx, |workspace, window, cx| {
24404            workspace.open_path(
24405                (worktree_id, rel_path("main.rs")),
24406                Some(pane_1.downgrade()),
24407                true,
24408                window,
24409                cx,
24410            )
24411        })
24412        .unwrap()
24413        .await
24414        .downcast::<Editor>()
24415        .unwrap();
24416    let _editor_2_reopened = workspace
24417        .update_in(cx, |workspace, window, cx| {
24418            workspace.open_path(
24419                (worktree_id, rel_path("main.rs")),
24420                Some(pane_2.downgrade()),
24421                true,
24422                window,
24423                cx,
24424            )
24425        })
24426        .unwrap()
24427        .await
24428        .downcast::<Editor>()
24429        .unwrap();
24430    pane_1.update(cx, |pane, cx| {
24431        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24432        open_editor.update(cx, |editor, cx| {
24433            assert_eq!(
24434                editor.display_text(cx),
24435                main_text,
24436                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24437            );
24438            assert_eq!(
24439                editor
24440                    .selections
24441                    .all::<Point>(&editor.display_snapshot(cx))
24442                    .into_iter()
24443                    .map(|s| s.range())
24444                    .collect::<Vec<_>>(),
24445                expected_ranges,
24446                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24447            );
24448        })
24449    });
24450    pane_2.update(cx, |pane, cx| {
24451        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24452        open_editor.update(cx, |editor, cx| {
24453            assert_eq!(
24454                editor.display_text(cx),
24455                r#"fn main() {
24456⋯rintln!("1");
24457⋯intln!("2");
24458⋯ntln!("3");
24459println!("4");
24460println!("5");
24461}"#,
24462                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24463            );
24464            assert_eq!(
24465                editor
24466                    .selections
24467                    .all::<Point>(&editor.display_snapshot(cx))
24468                    .into_iter()
24469                    .map(|s| s.range())
24470                    .collect::<Vec<_>>(),
24471                vec![Point::zero()..Point::zero()],
24472                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24473            );
24474        })
24475    });
24476}
24477
24478#[gpui::test]
24479async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24480    init_test(cx, |_| {});
24481
24482    let fs = FakeFs::new(cx.executor());
24483    let main_text = r#"fn main() {
24484println!("1");
24485println!("2");
24486println!("3");
24487println!("4");
24488println!("5");
24489}"#;
24490    let lib_text = "mod foo {}";
24491    fs.insert_tree(
24492        path!("/a"),
24493        json!({
24494            "lib.rs": lib_text,
24495            "main.rs": main_text,
24496        }),
24497    )
24498    .await;
24499
24500    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24501    let (workspace, cx) =
24502        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24503    let worktree_id = workspace.update(cx, |workspace, cx| {
24504        workspace.project().update(cx, |project, cx| {
24505            project.worktrees(cx).next().unwrap().read(cx).id()
24506        })
24507    });
24508
24509    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24510    let editor = workspace
24511        .update_in(cx, |workspace, window, cx| {
24512            workspace.open_path(
24513                (worktree_id, rel_path("main.rs")),
24514                Some(pane.downgrade()),
24515                true,
24516                window,
24517                cx,
24518            )
24519        })
24520        .unwrap()
24521        .await
24522        .downcast::<Editor>()
24523        .unwrap();
24524    pane.update(cx, |pane, cx| {
24525        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24526        open_editor.update(cx, |editor, cx| {
24527            assert_eq!(
24528                editor.display_text(cx),
24529                main_text,
24530                "Original main.rs text on initial open",
24531            );
24532        })
24533    });
24534    editor.update_in(cx, |editor, window, cx| {
24535        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24536    });
24537
24538    cx.update_global(|store: &mut SettingsStore, cx| {
24539        store.update_user_settings(cx, |s| {
24540            s.workspace.restore_on_file_reopen = Some(false);
24541        });
24542    });
24543    editor.update_in(cx, |editor, window, cx| {
24544        editor.fold_ranges(
24545            vec![
24546                Point::new(1, 0)..Point::new(1, 1),
24547                Point::new(2, 0)..Point::new(2, 2),
24548                Point::new(3, 0)..Point::new(3, 3),
24549            ],
24550            false,
24551            window,
24552            cx,
24553        );
24554    });
24555    pane.update_in(cx, |pane, window, cx| {
24556        pane.close_all_items(&CloseAllItems::default(), window, cx)
24557    })
24558    .await
24559    .unwrap();
24560    pane.update(cx, |pane, _| {
24561        assert!(pane.active_item().is_none());
24562    });
24563    cx.update_global(|store: &mut SettingsStore, cx| {
24564        store.update_user_settings(cx, |s| {
24565            s.workspace.restore_on_file_reopen = Some(true);
24566        });
24567    });
24568
24569    let _editor_reopened = workspace
24570        .update_in(cx, |workspace, window, cx| {
24571            workspace.open_path(
24572                (worktree_id, rel_path("main.rs")),
24573                Some(pane.downgrade()),
24574                true,
24575                window,
24576                cx,
24577            )
24578        })
24579        .unwrap()
24580        .await
24581        .downcast::<Editor>()
24582        .unwrap();
24583    pane.update(cx, |pane, cx| {
24584        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24585        open_editor.update(cx, |editor, cx| {
24586            assert_eq!(
24587                editor.display_text(cx),
24588                main_text,
24589                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24590            );
24591        })
24592    });
24593}
24594
24595#[gpui::test]
24596async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24597    struct EmptyModalView {
24598        focus_handle: gpui::FocusHandle,
24599    }
24600    impl EventEmitter<DismissEvent> for EmptyModalView {}
24601    impl Render for EmptyModalView {
24602        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24603            div()
24604        }
24605    }
24606    impl Focusable for EmptyModalView {
24607        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24608            self.focus_handle.clone()
24609        }
24610    }
24611    impl workspace::ModalView for EmptyModalView {}
24612    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24613        EmptyModalView {
24614            focus_handle: cx.focus_handle(),
24615        }
24616    }
24617
24618    init_test(cx, |_| {});
24619
24620    let fs = FakeFs::new(cx.executor());
24621    let project = Project::test(fs, [], cx).await;
24622    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24623    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24624    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24625    let editor = cx.new_window_entity(|window, cx| {
24626        Editor::new(
24627            EditorMode::full(),
24628            buffer,
24629            Some(project.clone()),
24630            window,
24631            cx,
24632        )
24633    });
24634    workspace
24635        .update(cx, |workspace, window, cx| {
24636            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24637        })
24638        .unwrap();
24639    editor.update_in(cx, |editor, window, cx| {
24640        editor.open_context_menu(&OpenContextMenu, window, cx);
24641        assert!(editor.mouse_context_menu.is_some());
24642    });
24643    workspace
24644        .update(cx, |workspace, window, cx| {
24645            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24646        })
24647        .unwrap();
24648    cx.read(|cx| {
24649        assert!(editor.read(cx).mouse_context_menu.is_none());
24650    });
24651}
24652
24653fn set_linked_edit_ranges(
24654    opening: (Point, Point),
24655    closing: (Point, Point),
24656    editor: &mut Editor,
24657    cx: &mut Context<Editor>,
24658) {
24659    let Some((buffer, _)) = editor
24660        .buffer
24661        .read(cx)
24662        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24663    else {
24664        panic!("Failed to get buffer for selection position");
24665    };
24666    let buffer = buffer.read(cx);
24667    let buffer_id = buffer.remote_id();
24668    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24669    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24670    let mut linked_ranges = HashMap::default();
24671    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24672    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24673}
24674
24675#[gpui::test]
24676async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24677    init_test(cx, |_| {});
24678
24679    let fs = FakeFs::new(cx.executor());
24680    fs.insert_file(path!("/file.html"), Default::default())
24681        .await;
24682
24683    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24684
24685    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24686    let html_language = Arc::new(Language::new(
24687        LanguageConfig {
24688            name: "HTML".into(),
24689            matcher: LanguageMatcher {
24690                path_suffixes: vec!["html".to_string()],
24691                ..LanguageMatcher::default()
24692            },
24693            brackets: BracketPairConfig {
24694                pairs: vec![BracketPair {
24695                    start: "<".into(),
24696                    end: ">".into(),
24697                    close: true,
24698                    ..Default::default()
24699                }],
24700                ..Default::default()
24701            },
24702            ..Default::default()
24703        },
24704        Some(tree_sitter_html::LANGUAGE.into()),
24705    ));
24706    language_registry.add(html_language);
24707    let mut fake_servers = language_registry.register_fake_lsp(
24708        "HTML",
24709        FakeLspAdapter {
24710            capabilities: lsp::ServerCapabilities {
24711                completion_provider: Some(lsp::CompletionOptions {
24712                    resolve_provider: Some(true),
24713                    ..Default::default()
24714                }),
24715                ..Default::default()
24716            },
24717            ..Default::default()
24718        },
24719    );
24720
24721    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24722    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24723
24724    let worktree_id = workspace
24725        .update(cx, |workspace, _window, cx| {
24726            workspace.project().update(cx, |project, cx| {
24727                project.worktrees(cx).next().unwrap().read(cx).id()
24728            })
24729        })
24730        .unwrap();
24731    project
24732        .update(cx, |project, cx| {
24733            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24734        })
24735        .await
24736        .unwrap();
24737    let editor = workspace
24738        .update(cx, |workspace, window, cx| {
24739            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24740        })
24741        .unwrap()
24742        .await
24743        .unwrap()
24744        .downcast::<Editor>()
24745        .unwrap();
24746
24747    let fake_server = fake_servers.next().await.unwrap();
24748    editor.update_in(cx, |editor, window, cx| {
24749        editor.set_text("<ad></ad>", window, cx);
24750        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24751            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24752        });
24753        set_linked_edit_ranges(
24754            (Point::new(0, 1), Point::new(0, 3)),
24755            (Point::new(0, 6), Point::new(0, 8)),
24756            editor,
24757            cx,
24758        );
24759    });
24760    let mut completion_handle =
24761        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24762            Ok(Some(lsp::CompletionResponse::Array(vec![
24763                lsp::CompletionItem {
24764                    label: "head".to_string(),
24765                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24766                        lsp::InsertReplaceEdit {
24767                            new_text: "head".to_string(),
24768                            insert: lsp::Range::new(
24769                                lsp::Position::new(0, 1),
24770                                lsp::Position::new(0, 3),
24771                            ),
24772                            replace: lsp::Range::new(
24773                                lsp::Position::new(0, 1),
24774                                lsp::Position::new(0, 3),
24775                            ),
24776                        },
24777                    )),
24778                    ..Default::default()
24779                },
24780            ])))
24781        });
24782    editor.update_in(cx, |editor, window, cx| {
24783        editor.show_completions(&ShowCompletions, window, cx);
24784    });
24785    cx.run_until_parked();
24786    completion_handle.next().await.unwrap();
24787    editor.update(cx, |editor, _| {
24788        assert!(
24789            editor.context_menu_visible(),
24790            "Completion menu should be visible"
24791        );
24792    });
24793    editor.update_in(cx, |editor, window, cx| {
24794        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24795    });
24796    cx.executor().run_until_parked();
24797    editor.update(cx, |editor, cx| {
24798        assert_eq!(editor.text(cx), "<head></head>");
24799    });
24800}
24801
24802#[gpui::test]
24803async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24804    init_test(cx, |_| {});
24805
24806    let mut cx = EditorTestContext::new(cx).await;
24807    let language = Arc::new(Language::new(
24808        LanguageConfig {
24809            name: "TSX".into(),
24810            matcher: LanguageMatcher {
24811                path_suffixes: vec!["tsx".to_string()],
24812                ..LanguageMatcher::default()
24813            },
24814            brackets: BracketPairConfig {
24815                pairs: vec![BracketPair {
24816                    start: "<".into(),
24817                    end: ">".into(),
24818                    close: true,
24819                    ..Default::default()
24820                }],
24821                ..Default::default()
24822            },
24823            linked_edit_characters: HashSet::from_iter(['.']),
24824            ..Default::default()
24825        },
24826        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24827    ));
24828    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24829
24830    // Test typing > does not extend linked pair
24831    cx.set_state("<divˇ<div></div>");
24832    cx.update_editor(|editor, _, cx| {
24833        set_linked_edit_ranges(
24834            (Point::new(0, 1), Point::new(0, 4)),
24835            (Point::new(0, 11), Point::new(0, 14)),
24836            editor,
24837            cx,
24838        );
24839    });
24840    cx.update_editor(|editor, window, cx| {
24841        editor.handle_input(">", window, cx);
24842    });
24843    cx.assert_editor_state("<div>ˇ<div></div>");
24844
24845    // Test typing . do extend linked pair
24846    cx.set_state("<Animatedˇ></Animated>");
24847    cx.update_editor(|editor, _, cx| {
24848        set_linked_edit_ranges(
24849            (Point::new(0, 1), Point::new(0, 9)),
24850            (Point::new(0, 12), Point::new(0, 20)),
24851            editor,
24852            cx,
24853        );
24854    });
24855    cx.update_editor(|editor, window, cx| {
24856        editor.handle_input(".", window, cx);
24857    });
24858    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24859    cx.update_editor(|editor, _, cx| {
24860        set_linked_edit_ranges(
24861            (Point::new(0, 1), Point::new(0, 10)),
24862            (Point::new(0, 13), Point::new(0, 21)),
24863            editor,
24864            cx,
24865        );
24866    });
24867    cx.update_editor(|editor, window, cx| {
24868        editor.handle_input("V", window, cx);
24869    });
24870    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24871}
24872
24873#[gpui::test]
24874async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24875    init_test(cx, |_| {});
24876
24877    let fs = FakeFs::new(cx.executor());
24878    fs.insert_tree(
24879        path!("/root"),
24880        json!({
24881            "a": {
24882                "main.rs": "fn main() {}",
24883            },
24884            "foo": {
24885                "bar": {
24886                    "external_file.rs": "pub mod external {}",
24887                }
24888            }
24889        }),
24890    )
24891    .await;
24892
24893    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24894    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24895    language_registry.add(rust_lang());
24896    let _fake_servers = language_registry.register_fake_lsp(
24897        "Rust",
24898        FakeLspAdapter {
24899            ..FakeLspAdapter::default()
24900        },
24901    );
24902    let (workspace, cx) =
24903        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24904    let worktree_id = workspace.update(cx, |workspace, cx| {
24905        workspace.project().update(cx, |project, cx| {
24906            project.worktrees(cx).next().unwrap().read(cx).id()
24907        })
24908    });
24909
24910    let assert_language_servers_count =
24911        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24912            project.update(cx, |project, cx| {
24913                let current = project
24914                    .lsp_store()
24915                    .read(cx)
24916                    .as_local()
24917                    .unwrap()
24918                    .language_servers
24919                    .len();
24920                assert_eq!(expected, current, "{context}");
24921            });
24922        };
24923
24924    assert_language_servers_count(
24925        0,
24926        "No servers should be running before any file is open",
24927        cx,
24928    );
24929    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24930    let main_editor = workspace
24931        .update_in(cx, |workspace, window, cx| {
24932            workspace.open_path(
24933                (worktree_id, rel_path("main.rs")),
24934                Some(pane.downgrade()),
24935                true,
24936                window,
24937                cx,
24938            )
24939        })
24940        .unwrap()
24941        .await
24942        .downcast::<Editor>()
24943        .unwrap();
24944    pane.update(cx, |pane, cx| {
24945        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24946        open_editor.update(cx, |editor, cx| {
24947            assert_eq!(
24948                editor.display_text(cx),
24949                "fn main() {}",
24950                "Original main.rs text on initial open",
24951            );
24952        });
24953        assert_eq!(open_editor, main_editor);
24954    });
24955    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24956
24957    let external_editor = workspace
24958        .update_in(cx, |workspace, window, cx| {
24959            workspace.open_abs_path(
24960                PathBuf::from("/root/foo/bar/external_file.rs"),
24961                OpenOptions::default(),
24962                window,
24963                cx,
24964            )
24965        })
24966        .await
24967        .expect("opening external file")
24968        .downcast::<Editor>()
24969        .expect("downcasted external file's open element to editor");
24970    pane.update(cx, |pane, cx| {
24971        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24972        open_editor.update(cx, |editor, cx| {
24973            assert_eq!(
24974                editor.display_text(cx),
24975                "pub mod external {}",
24976                "External file is open now",
24977            );
24978        });
24979        assert_eq!(open_editor, external_editor);
24980    });
24981    assert_language_servers_count(
24982        1,
24983        "Second, external, *.rs file should join the existing server",
24984        cx,
24985    );
24986
24987    pane.update_in(cx, |pane, window, cx| {
24988        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24989    })
24990    .await
24991    .unwrap();
24992    pane.update_in(cx, |pane, window, cx| {
24993        pane.navigate_backward(&Default::default(), window, cx);
24994    });
24995    cx.run_until_parked();
24996    pane.update(cx, |pane, cx| {
24997        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24998        open_editor.update(cx, |editor, cx| {
24999            assert_eq!(
25000                editor.display_text(cx),
25001                "pub mod external {}",
25002                "External file is open now",
25003            );
25004        });
25005    });
25006    assert_language_servers_count(
25007        1,
25008        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25009        cx,
25010    );
25011
25012    cx.update(|_, cx| {
25013        workspace::reload(cx);
25014    });
25015    assert_language_servers_count(
25016        1,
25017        "After reloading the worktree with local and external files opened, only one project should be started",
25018        cx,
25019    );
25020}
25021
25022#[gpui::test]
25023async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25024    init_test(cx, |_| {});
25025
25026    let mut cx = EditorTestContext::new(cx).await;
25027    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25028    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25029
25030    // test cursor move to start of each line on tab
25031    // for `if`, `elif`, `else`, `while`, `with` and `for`
25032    cx.set_state(indoc! {"
25033        def main():
25034        ˇ    for item in items:
25035        ˇ        while item.active:
25036        ˇ            if item.value > 10:
25037        ˇ                continue
25038        ˇ            elif item.value < 0:
25039        ˇ                break
25040        ˇ            else:
25041        ˇ                with item.context() as ctx:
25042        ˇ                    yield count
25043        ˇ        else:
25044        ˇ            log('while else')
25045        ˇ    else:
25046        ˇ        log('for else')
25047    "});
25048    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25049    cx.assert_editor_state(indoc! {"
25050        def main():
25051            ˇfor item in items:
25052                ˇwhile item.active:
25053                    ˇif item.value > 10:
25054                        ˇcontinue
25055                    ˇelif item.value < 0:
25056                        ˇbreak
25057                    ˇelse:
25058                        ˇwith item.context() as ctx:
25059                            ˇyield count
25060                ˇelse:
25061                    ˇlog('while else')
25062            ˇelse:
25063                ˇlog('for else')
25064    "});
25065    // test relative indent is preserved when tab
25066    // for `if`, `elif`, `else`, `while`, `with` and `for`
25067    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25068    cx.assert_editor_state(indoc! {"
25069        def main():
25070                ˇfor item in items:
25071                    ˇwhile item.active:
25072                        ˇif item.value > 10:
25073                            ˇcontinue
25074                        ˇelif item.value < 0:
25075                            ˇbreak
25076                        ˇelse:
25077                            ˇwith item.context() as ctx:
25078                                ˇyield count
25079                    ˇelse:
25080                        ˇlog('while else')
25081                ˇelse:
25082                    ˇlog('for else')
25083    "});
25084
25085    // test cursor move to start of each line on tab
25086    // for `try`, `except`, `else`, `finally`, `match` and `def`
25087    cx.set_state(indoc! {"
25088        def main():
25089        ˇ    try:
25090        ˇ        fetch()
25091        ˇ    except ValueError:
25092        ˇ        handle_error()
25093        ˇ    else:
25094        ˇ        match value:
25095        ˇ            case _:
25096        ˇ    finally:
25097        ˇ        def status():
25098        ˇ            return 0
25099    "});
25100    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25101    cx.assert_editor_state(indoc! {"
25102        def main():
25103            ˇtry:
25104                ˇfetch()
25105            ˇexcept ValueError:
25106                ˇhandle_error()
25107            ˇelse:
25108                ˇmatch value:
25109                    ˇcase _:
25110            ˇfinally:
25111                ˇdef status():
25112                    ˇreturn 0
25113    "});
25114    // test relative indent is preserved when tab
25115    // for `try`, `except`, `else`, `finally`, `match` and `def`
25116    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25117    cx.assert_editor_state(indoc! {"
25118        def main():
25119                ˇtry:
25120                    ˇfetch()
25121                ˇexcept ValueError:
25122                    ˇhandle_error()
25123                ˇelse:
25124                    ˇmatch value:
25125                        ˇcase _:
25126                ˇfinally:
25127                    ˇdef status():
25128                        ˇreturn 0
25129    "});
25130}
25131
25132#[gpui::test]
25133async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25134    init_test(cx, |_| {});
25135
25136    let mut cx = EditorTestContext::new(cx).await;
25137    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25138    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25139
25140    // test `else` auto outdents when typed inside `if` block
25141    cx.set_state(indoc! {"
25142        def main():
25143            if i == 2:
25144                return
25145                ˇ
25146    "});
25147    cx.update_editor(|editor, window, cx| {
25148        editor.handle_input("else:", window, cx);
25149    });
25150    cx.assert_editor_state(indoc! {"
25151        def main():
25152            if i == 2:
25153                return
25154            else:ˇ
25155    "});
25156
25157    // test `except` auto outdents when typed inside `try` block
25158    cx.set_state(indoc! {"
25159        def main():
25160            try:
25161                i = 2
25162                ˇ
25163    "});
25164    cx.update_editor(|editor, window, cx| {
25165        editor.handle_input("except:", window, cx);
25166    });
25167    cx.assert_editor_state(indoc! {"
25168        def main():
25169            try:
25170                i = 2
25171            except:ˇ
25172    "});
25173
25174    // test `else` auto outdents when typed inside `except` block
25175    cx.set_state(indoc! {"
25176        def main():
25177            try:
25178                i = 2
25179            except:
25180                j = 2
25181                ˇ
25182    "});
25183    cx.update_editor(|editor, window, cx| {
25184        editor.handle_input("else:", window, cx);
25185    });
25186    cx.assert_editor_state(indoc! {"
25187        def main():
25188            try:
25189                i = 2
25190            except:
25191                j = 2
25192            else:ˇ
25193    "});
25194
25195    // test `finally` auto outdents when typed inside `else` block
25196    cx.set_state(indoc! {"
25197        def main():
25198            try:
25199                i = 2
25200            except:
25201                j = 2
25202            else:
25203                k = 2
25204                ˇ
25205    "});
25206    cx.update_editor(|editor, window, cx| {
25207        editor.handle_input("finally:", window, cx);
25208    });
25209    cx.assert_editor_state(indoc! {"
25210        def main():
25211            try:
25212                i = 2
25213            except:
25214                j = 2
25215            else:
25216                k = 2
25217            finally:ˇ
25218    "});
25219
25220    // test `else` does not outdents when typed inside `except` block right after for block
25221    cx.set_state(indoc! {"
25222        def main():
25223            try:
25224                i = 2
25225            except:
25226                for i in range(n):
25227                    pass
25228                ˇ
25229    "});
25230    cx.update_editor(|editor, window, cx| {
25231        editor.handle_input("else:", window, cx);
25232    });
25233    cx.assert_editor_state(indoc! {"
25234        def main():
25235            try:
25236                i = 2
25237            except:
25238                for i in range(n):
25239                    pass
25240                else:ˇ
25241    "});
25242
25243    // test `finally` auto outdents when typed inside `else` block right after for block
25244    cx.set_state(indoc! {"
25245        def main():
25246            try:
25247                i = 2
25248            except:
25249                j = 2
25250            else:
25251                for i in range(n):
25252                    pass
25253                ˇ
25254    "});
25255    cx.update_editor(|editor, window, cx| {
25256        editor.handle_input("finally:", window, cx);
25257    });
25258    cx.assert_editor_state(indoc! {"
25259        def main():
25260            try:
25261                i = 2
25262            except:
25263                j = 2
25264            else:
25265                for i in range(n):
25266                    pass
25267            finally:ˇ
25268    "});
25269
25270    // test `except` outdents to inner "try" block
25271    cx.set_state(indoc! {"
25272        def main():
25273            try:
25274                i = 2
25275                if i == 2:
25276                    try:
25277                        i = 3
25278                        ˇ
25279    "});
25280    cx.update_editor(|editor, window, cx| {
25281        editor.handle_input("except:", window, cx);
25282    });
25283    cx.assert_editor_state(indoc! {"
25284        def main():
25285            try:
25286                i = 2
25287                if i == 2:
25288                    try:
25289                        i = 3
25290                    except:ˇ
25291    "});
25292
25293    // test `except` outdents to outer "try" block
25294    cx.set_state(indoc! {"
25295        def main():
25296            try:
25297                i = 2
25298                if i == 2:
25299                    try:
25300                        i = 3
25301                ˇ
25302    "});
25303    cx.update_editor(|editor, window, cx| {
25304        editor.handle_input("except:", window, cx);
25305    });
25306    cx.assert_editor_state(indoc! {"
25307        def main():
25308            try:
25309                i = 2
25310                if i == 2:
25311                    try:
25312                        i = 3
25313            except:ˇ
25314    "});
25315
25316    // test `else` stays at correct indent when typed after `for` block
25317    cx.set_state(indoc! {"
25318        def main():
25319            for i in range(10):
25320                if i == 3:
25321                    break
25322            ˇ
25323    "});
25324    cx.update_editor(|editor, window, cx| {
25325        editor.handle_input("else:", window, cx);
25326    });
25327    cx.assert_editor_state(indoc! {"
25328        def main():
25329            for i in range(10):
25330                if i == 3:
25331                    break
25332            else:ˇ
25333    "});
25334
25335    // test does not outdent on typing after line with square brackets
25336    cx.set_state(indoc! {"
25337        def f() -> list[str]:
25338            ˇ
25339    "});
25340    cx.update_editor(|editor, window, cx| {
25341        editor.handle_input("a", window, cx);
25342    });
25343    cx.assert_editor_state(indoc! {"
25344        def f() -> list[str]:
2534525346    "});
25347
25348    // test does not outdent on typing : after case keyword
25349    cx.set_state(indoc! {"
25350        match 1:
25351            caseˇ
25352    "});
25353    cx.update_editor(|editor, window, cx| {
25354        editor.handle_input(":", window, cx);
25355    });
25356    cx.assert_editor_state(indoc! {"
25357        match 1:
25358            case:ˇ
25359    "});
25360}
25361
25362#[gpui::test]
25363async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25364    init_test(cx, |_| {});
25365    update_test_language_settings(cx, |settings| {
25366        settings.defaults.extend_comment_on_newline = Some(false);
25367    });
25368    let mut cx = EditorTestContext::new(cx).await;
25369    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25370    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25371
25372    // test correct indent after newline on comment
25373    cx.set_state(indoc! {"
25374        # COMMENT:ˇ
25375    "});
25376    cx.update_editor(|editor, window, cx| {
25377        editor.newline(&Newline, window, cx);
25378    });
25379    cx.assert_editor_state(indoc! {"
25380        # COMMENT:
25381        ˇ
25382    "});
25383
25384    // test correct indent after newline in brackets
25385    cx.set_state(indoc! {"
25386        {ˇ}
25387    "});
25388    cx.update_editor(|editor, window, cx| {
25389        editor.newline(&Newline, window, cx);
25390    });
25391    cx.run_until_parked();
25392    cx.assert_editor_state(indoc! {"
25393        {
25394            ˇ
25395        }
25396    "});
25397
25398    cx.set_state(indoc! {"
25399        (ˇ)
25400    "});
25401    cx.update_editor(|editor, window, cx| {
25402        editor.newline(&Newline, window, cx);
25403    });
25404    cx.run_until_parked();
25405    cx.assert_editor_state(indoc! {"
25406        (
25407            ˇ
25408        )
25409    "});
25410
25411    // do not indent after empty lists or dictionaries
25412    cx.set_state(indoc! {"
25413        a = []ˇ
25414    "});
25415    cx.update_editor(|editor, window, cx| {
25416        editor.newline(&Newline, window, cx);
25417    });
25418    cx.run_until_parked();
25419    cx.assert_editor_state(indoc! {"
25420        a = []
25421        ˇ
25422    "});
25423}
25424
25425#[gpui::test]
25426async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25427    init_test(cx, |_| {});
25428
25429    let mut cx = EditorTestContext::new(cx).await;
25430    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25431    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25432
25433    // test cursor move to start of each line on tab
25434    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25435    cx.set_state(indoc! {"
25436        function main() {
25437        ˇ    for item in $items; do
25438        ˇ        while [ -n \"$item\" ]; do
25439        ˇ            if [ \"$value\" -gt 10 ]; then
25440        ˇ                continue
25441        ˇ            elif [ \"$value\" -lt 0 ]; then
25442        ˇ                break
25443        ˇ            else
25444        ˇ                echo \"$item\"
25445        ˇ            fi
25446        ˇ        done
25447        ˇ    done
25448        ˇ}
25449    "});
25450    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25451    cx.assert_editor_state(indoc! {"
25452        function main() {
25453            ˇfor item in $items; do
25454                ˇwhile [ -n \"$item\" ]; do
25455                    ˇif [ \"$value\" -gt 10 ]; then
25456                        ˇcontinue
25457                    ˇelif [ \"$value\" -lt 0 ]; then
25458                        ˇbreak
25459                    ˇelse
25460                        ˇecho \"$item\"
25461                    ˇfi
25462                ˇdone
25463            ˇdone
25464        ˇ}
25465    "});
25466    // test relative indent is preserved when tab
25467    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25468    cx.assert_editor_state(indoc! {"
25469        function main() {
25470                ˇfor item in $items; do
25471                    ˇwhile [ -n \"$item\" ]; do
25472                        ˇif [ \"$value\" -gt 10 ]; then
25473                            ˇcontinue
25474                        ˇelif [ \"$value\" -lt 0 ]; then
25475                            ˇbreak
25476                        ˇelse
25477                            ˇecho \"$item\"
25478                        ˇfi
25479                    ˇdone
25480                ˇdone
25481            ˇ}
25482    "});
25483
25484    // test cursor move to start of each line on tab
25485    // for `case` statement with patterns
25486    cx.set_state(indoc! {"
25487        function handle() {
25488        ˇ    case \"$1\" in
25489        ˇ        start)
25490        ˇ            echo \"a\"
25491        ˇ            ;;
25492        ˇ        stop)
25493        ˇ            echo \"b\"
25494        ˇ            ;;
25495        ˇ        *)
25496        ˇ            echo \"c\"
25497        ˇ            ;;
25498        ˇ    esac
25499        ˇ}
25500    "});
25501    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25502    cx.assert_editor_state(indoc! {"
25503        function handle() {
25504            ˇcase \"$1\" in
25505                ˇstart)
25506                    ˇecho \"a\"
25507                    ˇ;;
25508                ˇstop)
25509                    ˇecho \"b\"
25510                    ˇ;;
25511                ˇ*)
25512                    ˇecho \"c\"
25513                    ˇ;;
25514            ˇesac
25515        ˇ}
25516    "});
25517}
25518
25519#[gpui::test]
25520async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25521    init_test(cx, |_| {});
25522
25523    let mut cx = EditorTestContext::new(cx).await;
25524    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25525    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25526
25527    // test indents on comment insert
25528    cx.set_state(indoc! {"
25529        function main() {
25530        ˇ    for item in $items; do
25531        ˇ        while [ -n \"$item\" ]; do
25532        ˇ            if [ \"$value\" -gt 10 ]; then
25533        ˇ                continue
25534        ˇ            elif [ \"$value\" -lt 0 ]; then
25535        ˇ                break
25536        ˇ            else
25537        ˇ                echo \"$item\"
25538        ˇ            fi
25539        ˇ        done
25540        ˇ    done
25541        ˇ}
25542    "});
25543    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25544    cx.assert_editor_state(indoc! {"
25545        function main() {
25546        #ˇ    for item in $items; do
25547        #ˇ        while [ -n \"$item\" ]; do
25548        #ˇ            if [ \"$value\" -gt 10 ]; then
25549        #ˇ                continue
25550        #ˇ            elif [ \"$value\" -lt 0 ]; then
25551        #ˇ                break
25552        #ˇ            else
25553        #ˇ                echo \"$item\"
25554        #ˇ            fi
25555        #ˇ        done
25556        #ˇ    done
25557        #ˇ}
25558    "});
25559}
25560
25561#[gpui::test]
25562async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25563    init_test(cx, |_| {});
25564
25565    let mut cx = EditorTestContext::new(cx).await;
25566    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25567    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25568
25569    // test `else` auto outdents when typed inside `if` block
25570    cx.set_state(indoc! {"
25571        if [ \"$1\" = \"test\" ]; then
25572            echo \"foo bar\"
25573            ˇ
25574    "});
25575    cx.update_editor(|editor, window, cx| {
25576        editor.handle_input("else", window, cx);
25577    });
25578    cx.assert_editor_state(indoc! {"
25579        if [ \"$1\" = \"test\" ]; then
25580            echo \"foo bar\"
25581        elseˇ
25582    "});
25583
25584    // test `elif` auto outdents when typed inside `if` block
25585    cx.set_state(indoc! {"
25586        if [ \"$1\" = \"test\" ]; then
25587            echo \"foo bar\"
25588            ˇ
25589    "});
25590    cx.update_editor(|editor, window, cx| {
25591        editor.handle_input("elif", window, cx);
25592    });
25593    cx.assert_editor_state(indoc! {"
25594        if [ \"$1\" = \"test\" ]; then
25595            echo \"foo bar\"
25596        elifˇ
25597    "});
25598
25599    // test `fi` auto outdents when typed inside `else` block
25600    cx.set_state(indoc! {"
25601        if [ \"$1\" = \"test\" ]; then
25602            echo \"foo bar\"
25603        else
25604            echo \"bar baz\"
25605            ˇ
25606    "});
25607    cx.update_editor(|editor, window, cx| {
25608        editor.handle_input("fi", window, cx);
25609    });
25610    cx.assert_editor_state(indoc! {"
25611        if [ \"$1\" = \"test\" ]; then
25612            echo \"foo bar\"
25613        else
25614            echo \"bar baz\"
25615        fiˇ
25616    "});
25617
25618    // test `done` auto outdents when typed inside `while` block
25619    cx.set_state(indoc! {"
25620        while read line; do
25621            echo \"$line\"
25622            ˇ
25623    "});
25624    cx.update_editor(|editor, window, cx| {
25625        editor.handle_input("done", window, cx);
25626    });
25627    cx.assert_editor_state(indoc! {"
25628        while read line; do
25629            echo \"$line\"
25630        doneˇ
25631    "});
25632
25633    // test `done` auto outdents when typed inside `for` block
25634    cx.set_state(indoc! {"
25635        for file in *.txt; do
25636            cat \"$file\"
25637            ˇ
25638    "});
25639    cx.update_editor(|editor, window, cx| {
25640        editor.handle_input("done", window, cx);
25641    });
25642    cx.assert_editor_state(indoc! {"
25643        for file in *.txt; do
25644            cat \"$file\"
25645        doneˇ
25646    "});
25647
25648    // test `esac` auto outdents when typed inside `case` block
25649    cx.set_state(indoc! {"
25650        case \"$1\" in
25651            start)
25652                echo \"foo bar\"
25653                ;;
25654            stop)
25655                echo \"bar baz\"
25656                ;;
25657            ˇ
25658    "});
25659    cx.update_editor(|editor, window, cx| {
25660        editor.handle_input("esac", window, cx);
25661    });
25662    cx.assert_editor_state(indoc! {"
25663        case \"$1\" in
25664            start)
25665                echo \"foo bar\"
25666                ;;
25667            stop)
25668                echo \"bar baz\"
25669                ;;
25670        esacˇ
25671    "});
25672
25673    // test `*)` auto outdents when typed inside `case` block
25674    cx.set_state(indoc! {"
25675        case \"$1\" in
25676            start)
25677                echo \"foo bar\"
25678                ;;
25679                ˇ
25680    "});
25681    cx.update_editor(|editor, window, cx| {
25682        editor.handle_input("*)", window, cx);
25683    });
25684    cx.assert_editor_state(indoc! {"
25685        case \"$1\" in
25686            start)
25687                echo \"foo bar\"
25688                ;;
25689            *)ˇ
25690    "});
25691
25692    // test `fi` outdents to correct level with nested if blocks
25693    cx.set_state(indoc! {"
25694        if [ \"$1\" = \"test\" ]; then
25695            echo \"outer if\"
25696            if [ \"$2\" = \"debug\" ]; then
25697                echo \"inner if\"
25698                ˇ
25699    "});
25700    cx.update_editor(|editor, window, cx| {
25701        editor.handle_input("fi", window, cx);
25702    });
25703    cx.assert_editor_state(indoc! {"
25704        if [ \"$1\" = \"test\" ]; then
25705            echo \"outer if\"
25706            if [ \"$2\" = \"debug\" ]; then
25707                echo \"inner if\"
25708            fiˇ
25709    "});
25710}
25711
25712#[gpui::test]
25713async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25714    init_test(cx, |_| {});
25715    update_test_language_settings(cx, |settings| {
25716        settings.defaults.extend_comment_on_newline = Some(false);
25717    });
25718    let mut cx = EditorTestContext::new(cx).await;
25719    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25720    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25721
25722    // test correct indent after newline on comment
25723    cx.set_state(indoc! {"
25724        # COMMENT:ˇ
25725    "});
25726    cx.update_editor(|editor, window, cx| {
25727        editor.newline(&Newline, window, cx);
25728    });
25729    cx.assert_editor_state(indoc! {"
25730        # COMMENT:
25731        ˇ
25732    "});
25733
25734    // test correct indent after newline after `then`
25735    cx.set_state(indoc! {"
25736
25737        if [ \"$1\" = \"test\" ]; thenˇ
25738    "});
25739    cx.update_editor(|editor, window, cx| {
25740        editor.newline(&Newline, window, cx);
25741    });
25742    cx.run_until_parked();
25743    cx.assert_editor_state(indoc! {"
25744
25745        if [ \"$1\" = \"test\" ]; then
25746            ˇ
25747    "});
25748
25749    // test correct indent after newline after `else`
25750    cx.set_state(indoc! {"
25751        if [ \"$1\" = \"test\" ]; then
25752        elseˇ
25753    "});
25754    cx.update_editor(|editor, window, cx| {
25755        editor.newline(&Newline, window, cx);
25756    });
25757    cx.run_until_parked();
25758    cx.assert_editor_state(indoc! {"
25759        if [ \"$1\" = \"test\" ]; then
25760        else
25761            ˇ
25762    "});
25763
25764    // test correct indent after newline after `elif`
25765    cx.set_state(indoc! {"
25766        if [ \"$1\" = \"test\" ]; then
25767        elifˇ
25768    "});
25769    cx.update_editor(|editor, window, cx| {
25770        editor.newline(&Newline, window, cx);
25771    });
25772    cx.run_until_parked();
25773    cx.assert_editor_state(indoc! {"
25774        if [ \"$1\" = \"test\" ]; then
25775        elif
25776            ˇ
25777    "});
25778
25779    // test correct indent after newline after `do`
25780    cx.set_state(indoc! {"
25781        for file in *.txt; doˇ
25782    "});
25783    cx.update_editor(|editor, window, cx| {
25784        editor.newline(&Newline, window, cx);
25785    });
25786    cx.run_until_parked();
25787    cx.assert_editor_state(indoc! {"
25788        for file in *.txt; do
25789            ˇ
25790    "});
25791
25792    // test correct indent after newline after case pattern
25793    cx.set_state(indoc! {"
25794        case \"$1\" in
25795            start)ˇ
25796    "});
25797    cx.update_editor(|editor, window, cx| {
25798        editor.newline(&Newline, window, cx);
25799    });
25800    cx.run_until_parked();
25801    cx.assert_editor_state(indoc! {"
25802        case \"$1\" in
25803            start)
25804                ˇ
25805    "});
25806
25807    // test correct indent after newline after case pattern
25808    cx.set_state(indoc! {"
25809        case \"$1\" in
25810            start)
25811                ;;
25812            *)ˇ
25813    "});
25814    cx.update_editor(|editor, window, cx| {
25815        editor.newline(&Newline, window, cx);
25816    });
25817    cx.run_until_parked();
25818    cx.assert_editor_state(indoc! {"
25819        case \"$1\" in
25820            start)
25821                ;;
25822            *)
25823                ˇ
25824    "});
25825
25826    // test correct indent after newline after function opening brace
25827    cx.set_state(indoc! {"
25828        function test() {ˇ}
25829    "});
25830    cx.update_editor(|editor, window, cx| {
25831        editor.newline(&Newline, window, cx);
25832    });
25833    cx.run_until_parked();
25834    cx.assert_editor_state(indoc! {"
25835        function test() {
25836            ˇ
25837        }
25838    "});
25839
25840    // test no extra indent after semicolon on same line
25841    cx.set_state(indoc! {"
25842        echo \"test\"25843    "});
25844    cx.update_editor(|editor, window, cx| {
25845        editor.newline(&Newline, window, cx);
25846    });
25847    cx.run_until_parked();
25848    cx.assert_editor_state(indoc! {"
25849        echo \"test\";
25850        ˇ
25851    "});
25852}
25853
25854fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25855    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25856    point..point
25857}
25858
25859#[track_caller]
25860fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25861    let (text, ranges) = marked_text_ranges(marked_text, true);
25862    assert_eq!(editor.text(cx), text);
25863    assert_eq!(
25864        editor.selections.ranges(&editor.display_snapshot(cx)),
25865        ranges
25866            .iter()
25867            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
25868            .collect::<Vec<_>>(),
25869        "Assert selections are {}",
25870        marked_text
25871    );
25872}
25873
25874pub fn handle_signature_help_request(
25875    cx: &mut EditorLspTestContext,
25876    mocked_response: lsp::SignatureHelp,
25877) -> impl Future<Output = ()> + use<> {
25878    let mut request =
25879        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25880            let mocked_response = mocked_response.clone();
25881            async move { Ok(Some(mocked_response)) }
25882        });
25883
25884    async move {
25885        request.next().await;
25886    }
25887}
25888
25889#[track_caller]
25890pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25891    cx.update_editor(|editor, _, _| {
25892        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25893            let entries = menu.entries.borrow();
25894            let entries = entries
25895                .iter()
25896                .map(|entry| entry.string.as_str())
25897                .collect::<Vec<_>>();
25898            assert_eq!(entries, expected);
25899        } else {
25900            panic!("Expected completions menu");
25901        }
25902    });
25903}
25904
25905#[gpui::test]
25906async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
25907    init_test(cx, |_| {});
25908    let mut cx = EditorLspTestContext::new_rust(
25909        lsp::ServerCapabilities {
25910            completion_provider: Some(lsp::CompletionOptions {
25911                ..Default::default()
25912            }),
25913            ..Default::default()
25914        },
25915        cx,
25916    )
25917    .await;
25918    cx.lsp
25919        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25920            Ok(Some(lsp::CompletionResponse::Array(vec![
25921                lsp::CompletionItem {
25922                    label: "unsafe".into(),
25923                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25924                        range: lsp::Range {
25925                            start: lsp::Position {
25926                                line: 0,
25927                                character: 9,
25928                            },
25929                            end: lsp::Position {
25930                                line: 0,
25931                                character: 11,
25932                            },
25933                        },
25934                        new_text: "unsafe".to_string(),
25935                    })),
25936                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
25937                    ..Default::default()
25938                },
25939            ])))
25940        });
25941
25942    cx.update_editor(|editor, _, cx| {
25943        editor.project().unwrap().update(cx, |project, cx| {
25944            project.snippets().update(cx, |snippets, _cx| {
25945                snippets.add_snippet_for_test(
25946                    None,
25947                    PathBuf::from("test_snippets.json"),
25948                    vec![
25949                        Arc::new(project::snippet_provider::Snippet {
25950                            prefix: vec![
25951                                "unlimited word count".to_string(),
25952                                "unlimit word count".to_string(),
25953                                "unlimited unknown".to_string(),
25954                            ],
25955                            body: "this is many words".to_string(),
25956                            description: Some("description".to_string()),
25957                            name: "multi-word snippet test".to_string(),
25958                        }),
25959                        Arc::new(project::snippet_provider::Snippet {
25960                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
25961                            body: "fewer words".to_string(),
25962                            description: Some("alt description".to_string()),
25963                            name: "other name".to_string(),
25964                        }),
25965                        Arc::new(project::snippet_provider::Snippet {
25966                            prefix: vec!["ab aa".to_string()],
25967                            body: "abcd".to_string(),
25968                            description: None,
25969                            name: "alphabet".to_string(),
25970                        }),
25971                    ],
25972                );
25973            });
25974        })
25975    });
25976
25977    let get_completions = |cx: &mut EditorLspTestContext| {
25978        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
25979            Some(CodeContextMenu::Completions(context_menu)) => {
25980                let entries = context_menu.entries.borrow();
25981                entries
25982                    .iter()
25983                    .map(|entry| entry.string.clone())
25984                    .collect_vec()
25985            }
25986            _ => vec![],
25987        })
25988    };
25989
25990    // snippets:
25991    //  @foo
25992    //  foo bar
25993    //
25994    // when typing:
25995    //
25996    // when typing:
25997    //  - if I type a symbol "open the completions with snippets only"
25998    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
25999    //
26000    // stuff we need:
26001    //  - filtering logic change?
26002    //  - remember how far back the completion started.
26003
26004    let test_cases: &[(&str, &[&str])] = &[
26005        (
26006            "un",
26007            &[
26008                "unsafe",
26009                "unlimit word count",
26010                "unlimited unknown",
26011                "unlimited word count",
26012                "unsnip",
26013            ],
26014        ),
26015        (
26016            "u ",
26017            &[
26018                "unlimit word count",
26019                "unlimited unknown",
26020                "unlimited word count",
26021            ],
26022        ),
26023        ("u a", &["ab aa", "unsafe"]), // unsAfe
26024        (
26025            "u u",
26026            &[
26027                "unsafe",
26028                "unlimit word count",
26029                "unlimited unknown", // ranked highest among snippets
26030                "unlimited word count",
26031                "unsnip",
26032            ],
26033        ),
26034        ("uw c", &["unlimit word count", "unlimited word count"]),
26035        (
26036            "u w",
26037            &[
26038                "unlimit word count",
26039                "unlimited word count",
26040                "unlimited unknown",
26041            ],
26042        ),
26043        ("u w ", &["unlimit word count", "unlimited word count"]),
26044        (
26045            "u ",
26046            &[
26047                "unlimit word count",
26048                "unlimited unknown",
26049                "unlimited word count",
26050            ],
26051        ),
26052        ("wor", &[]),
26053        ("uf", &["unsafe"]),
26054        ("af", &["unsafe"]),
26055        ("afu", &[]),
26056        (
26057            "ue",
26058            &["unsafe", "unlimited unknown", "unlimited word count"],
26059        ),
26060        ("@", &["@few"]),
26061        ("@few", &["@few"]),
26062        ("@ ", &[]),
26063        ("a@", &["@few"]),
26064        ("a@f", &["@few", "unsafe"]),
26065        ("a@fw", &["@few"]),
26066        ("a", &["ab aa", "unsafe"]),
26067        ("aa", &["ab aa"]),
26068        ("aaa", &["ab aa"]),
26069        ("ab", &["ab aa"]),
26070        ("ab ", &["ab aa"]),
26071        ("ab a", &["ab aa", "unsafe"]),
26072        ("ab ab", &["ab aa"]),
26073        ("ab ab aa", &["ab aa"]),
26074    ];
26075
26076    for &(input_to_simulate, expected_completions) in test_cases {
26077        cx.set_state("fn a() { ˇ }\n");
26078        for c in input_to_simulate.split("") {
26079            cx.simulate_input(c);
26080            cx.run_until_parked();
26081        }
26082        let expected_completions = expected_completions
26083            .iter()
26084            .map(|s| s.to_string())
26085            .collect_vec();
26086        assert_eq!(
26087            get_completions(&mut cx),
26088            expected_completions,
26089            "< actual / expected >, input = {input_to_simulate:?}",
26090        );
26091    }
26092}
26093
26094/// Handle completion request passing a marked string specifying where the completion
26095/// should be triggered from using '|' character, what range should be replaced, and what completions
26096/// should be returned using '<' and '>' to delimit the range.
26097///
26098/// Also see `handle_completion_request_with_insert_and_replace`.
26099#[track_caller]
26100pub fn handle_completion_request(
26101    marked_string: &str,
26102    completions: Vec<&'static str>,
26103    is_incomplete: bool,
26104    counter: Arc<AtomicUsize>,
26105    cx: &mut EditorLspTestContext,
26106) -> impl Future<Output = ()> {
26107    let complete_from_marker: TextRangeMarker = '|'.into();
26108    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26109    let (_, mut marked_ranges) = marked_text_ranges_by(
26110        marked_string,
26111        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26112    );
26113
26114    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26115        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26116    ));
26117    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26118    let replace_range =
26119        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26120
26121    let mut request =
26122        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26123            let completions = completions.clone();
26124            counter.fetch_add(1, atomic::Ordering::Release);
26125            async move {
26126                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26127                assert_eq!(
26128                    params.text_document_position.position,
26129                    complete_from_position
26130                );
26131                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26132                    is_incomplete,
26133                    item_defaults: None,
26134                    items: completions
26135                        .iter()
26136                        .map(|completion_text| lsp::CompletionItem {
26137                            label: completion_text.to_string(),
26138                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26139                                range: replace_range,
26140                                new_text: completion_text.to_string(),
26141                            })),
26142                            ..Default::default()
26143                        })
26144                        .collect(),
26145                })))
26146            }
26147        });
26148
26149    async move {
26150        request.next().await;
26151    }
26152}
26153
26154/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26155/// given instead, which also contains an `insert` range.
26156///
26157/// This function uses markers to define ranges:
26158/// - `|` marks the cursor position
26159/// - `<>` marks the replace range
26160/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26161pub fn handle_completion_request_with_insert_and_replace(
26162    cx: &mut EditorLspTestContext,
26163    marked_string: &str,
26164    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26165    counter: Arc<AtomicUsize>,
26166) -> impl Future<Output = ()> {
26167    let complete_from_marker: TextRangeMarker = '|'.into();
26168    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26169    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26170
26171    let (_, mut marked_ranges) = marked_text_ranges_by(
26172        marked_string,
26173        vec![
26174            complete_from_marker.clone(),
26175            replace_range_marker.clone(),
26176            insert_range_marker.clone(),
26177        ],
26178    );
26179
26180    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26181        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26182    ));
26183    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26184    let replace_range =
26185        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26186
26187    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26188        Some(ranges) if !ranges.is_empty() => {
26189            let range1 = ranges[0].clone();
26190            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26191        }
26192        _ => lsp::Range {
26193            start: replace_range.start,
26194            end: complete_from_position,
26195        },
26196    };
26197
26198    let mut request =
26199        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26200            let completions = completions.clone();
26201            counter.fetch_add(1, atomic::Ordering::Release);
26202            async move {
26203                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26204                assert_eq!(
26205                    params.text_document_position.position, complete_from_position,
26206                    "marker `|` position doesn't match",
26207                );
26208                Ok(Some(lsp::CompletionResponse::Array(
26209                    completions
26210                        .iter()
26211                        .map(|(label, new_text)| lsp::CompletionItem {
26212                            label: label.to_string(),
26213                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26214                                lsp::InsertReplaceEdit {
26215                                    insert: insert_range,
26216                                    replace: replace_range,
26217                                    new_text: new_text.to_string(),
26218                                },
26219                            )),
26220                            ..Default::default()
26221                        })
26222                        .collect(),
26223                )))
26224            }
26225        });
26226
26227    async move {
26228        request.next().await;
26229    }
26230}
26231
26232fn handle_resolve_completion_request(
26233    cx: &mut EditorLspTestContext,
26234    edits: Option<Vec<(&'static str, &'static str)>>,
26235) -> impl Future<Output = ()> {
26236    let edits = edits.map(|edits| {
26237        edits
26238            .iter()
26239            .map(|(marked_string, new_text)| {
26240                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26241                let replace_range = cx.to_lsp_range(
26242                    MultiBufferOffset(marked_ranges[0].start)
26243                        ..MultiBufferOffset(marked_ranges[0].end),
26244                );
26245                lsp::TextEdit::new(replace_range, new_text.to_string())
26246            })
26247            .collect::<Vec<_>>()
26248    });
26249
26250    let mut request =
26251        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26252            let edits = edits.clone();
26253            async move {
26254                Ok(lsp::CompletionItem {
26255                    additional_text_edits: edits,
26256                    ..Default::default()
26257                })
26258            }
26259        });
26260
26261    async move {
26262        request.next().await;
26263    }
26264}
26265
26266pub(crate) fn update_test_language_settings(
26267    cx: &mut TestAppContext,
26268    f: impl Fn(&mut AllLanguageSettingsContent),
26269) {
26270    cx.update(|cx| {
26271        SettingsStore::update_global(cx, |store, cx| {
26272            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26273        });
26274    });
26275}
26276
26277pub(crate) fn update_test_project_settings(
26278    cx: &mut TestAppContext,
26279    f: impl Fn(&mut ProjectSettingsContent),
26280) {
26281    cx.update(|cx| {
26282        SettingsStore::update_global(cx, |store, cx| {
26283            store.update_user_settings(cx, |settings| f(&mut settings.project));
26284        });
26285    });
26286}
26287
26288pub(crate) fn update_test_editor_settings(
26289    cx: &mut TestAppContext,
26290    f: impl Fn(&mut EditorSettingsContent),
26291) {
26292    cx.update(|cx| {
26293        SettingsStore::update_global(cx, |store, cx| {
26294            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26295        })
26296    })
26297}
26298
26299pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26300    cx.update(|cx| {
26301        assets::Assets.load_test_fonts(cx);
26302        let store = SettingsStore::test(cx);
26303        cx.set_global(store);
26304        theme::init(theme::LoadThemes::JustBase, cx);
26305        release_channel::init(semver::Version::new(0, 0, 0), cx);
26306        crate::init(cx);
26307    });
26308    zlog::init_test();
26309    update_test_language_settings(cx, f);
26310}
26311
26312#[track_caller]
26313fn assert_hunk_revert(
26314    not_reverted_text_with_selections: &str,
26315    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26316    expected_reverted_text_with_selections: &str,
26317    base_text: &str,
26318    cx: &mut EditorLspTestContext,
26319) {
26320    cx.set_state(not_reverted_text_with_selections);
26321    cx.set_head_text(base_text);
26322    cx.executor().run_until_parked();
26323
26324    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26325        let snapshot = editor.snapshot(window, cx);
26326        let reverted_hunk_statuses = snapshot
26327            .buffer_snapshot()
26328            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26329            .map(|hunk| hunk.status().kind)
26330            .collect::<Vec<_>>();
26331
26332        editor.git_restore(&Default::default(), window, cx);
26333        reverted_hunk_statuses
26334    });
26335    cx.executor().run_until_parked();
26336    cx.assert_editor_state(expected_reverted_text_with_selections);
26337    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26338}
26339
26340#[gpui::test(iterations = 10)]
26341async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26342    init_test(cx, |_| {});
26343
26344    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26345    let counter = diagnostic_requests.clone();
26346
26347    let fs = FakeFs::new(cx.executor());
26348    fs.insert_tree(
26349        path!("/a"),
26350        json!({
26351            "first.rs": "fn main() { let a = 5; }",
26352            "second.rs": "// Test file",
26353        }),
26354    )
26355    .await;
26356
26357    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26358    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26359    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26360
26361    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26362    language_registry.add(rust_lang());
26363    let mut fake_servers = language_registry.register_fake_lsp(
26364        "Rust",
26365        FakeLspAdapter {
26366            capabilities: lsp::ServerCapabilities {
26367                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26368                    lsp::DiagnosticOptions {
26369                        identifier: None,
26370                        inter_file_dependencies: true,
26371                        workspace_diagnostics: true,
26372                        work_done_progress_options: Default::default(),
26373                    },
26374                )),
26375                ..Default::default()
26376            },
26377            ..Default::default()
26378        },
26379    );
26380
26381    let editor = workspace
26382        .update(cx, |workspace, window, cx| {
26383            workspace.open_abs_path(
26384                PathBuf::from(path!("/a/first.rs")),
26385                OpenOptions::default(),
26386                window,
26387                cx,
26388            )
26389        })
26390        .unwrap()
26391        .await
26392        .unwrap()
26393        .downcast::<Editor>()
26394        .unwrap();
26395    let fake_server = fake_servers.next().await.unwrap();
26396    let server_id = fake_server.server.server_id();
26397    let mut first_request = fake_server
26398        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26399            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26400            let result_id = Some(new_result_id.to_string());
26401            assert_eq!(
26402                params.text_document.uri,
26403                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26404            );
26405            async move {
26406                Ok(lsp::DocumentDiagnosticReportResult::Report(
26407                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26408                        related_documents: None,
26409                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26410                            items: Vec::new(),
26411                            result_id,
26412                        },
26413                    }),
26414                ))
26415            }
26416        });
26417
26418    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
26419        project.update(cx, |project, cx| {
26420            let buffer_id = editor
26421                .read(cx)
26422                .buffer()
26423                .read(cx)
26424                .as_singleton()
26425                .expect("created a singleton buffer")
26426                .read(cx)
26427                .remote_id();
26428            let buffer_result_id = project
26429                .lsp_store()
26430                .read(cx)
26431                .result_id(server_id, buffer_id, cx);
26432            assert_eq!(expected, buffer_result_id);
26433        });
26434    };
26435
26436    ensure_result_id(None, cx);
26437    cx.executor().advance_clock(Duration::from_millis(60));
26438    cx.executor().run_until_parked();
26439    assert_eq!(
26440        diagnostic_requests.load(atomic::Ordering::Acquire),
26441        1,
26442        "Opening file should trigger diagnostic request"
26443    );
26444    first_request
26445        .next()
26446        .await
26447        .expect("should have sent the first diagnostics pull request");
26448    ensure_result_id(Some("1".to_string()), cx);
26449
26450    // Editing should trigger diagnostics
26451    editor.update_in(cx, |editor, window, cx| {
26452        editor.handle_input("2", window, cx)
26453    });
26454    cx.executor().advance_clock(Duration::from_millis(60));
26455    cx.executor().run_until_parked();
26456    assert_eq!(
26457        diagnostic_requests.load(atomic::Ordering::Acquire),
26458        2,
26459        "Editing should trigger diagnostic request"
26460    );
26461    ensure_result_id(Some("2".to_string()), cx);
26462
26463    // Moving cursor should not trigger diagnostic request
26464    editor.update_in(cx, |editor, window, cx| {
26465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26466            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26467        });
26468    });
26469    cx.executor().advance_clock(Duration::from_millis(60));
26470    cx.executor().run_until_parked();
26471    assert_eq!(
26472        diagnostic_requests.load(atomic::Ordering::Acquire),
26473        2,
26474        "Cursor movement should not trigger diagnostic request"
26475    );
26476    ensure_result_id(Some("2".to_string()), cx);
26477    // Multiple rapid edits should be debounced
26478    for _ in 0..5 {
26479        editor.update_in(cx, |editor, window, cx| {
26480            editor.handle_input("x", window, cx)
26481        });
26482    }
26483    cx.executor().advance_clock(Duration::from_millis(60));
26484    cx.executor().run_until_parked();
26485
26486    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26487    assert!(
26488        final_requests <= 4,
26489        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26490    );
26491    ensure_result_id(Some(final_requests.to_string()), cx);
26492}
26493
26494#[gpui::test]
26495async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26496    // Regression test for issue #11671
26497    // Previously, adding a cursor after moving multiple cursors would reset
26498    // the cursor count instead of adding to the existing cursors.
26499    init_test(cx, |_| {});
26500    let mut cx = EditorTestContext::new(cx).await;
26501
26502    // Create a simple buffer with cursor at start
26503    cx.set_state(indoc! {"
26504        ˇaaaa
26505        bbbb
26506        cccc
26507        dddd
26508        eeee
26509        ffff
26510        gggg
26511        hhhh"});
26512
26513    // Add 2 cursors below (so we have 3 total)
26514    cx.update_editor(|editor, window, cx| {
26515        editor.add_selection_below(&Default::default(), window, cx);
26516        editor.add_selection_below(&Default::default(), window, cx);
26517    });
26518
26519    // Verify we have 3 cursors
26520    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26521    assert_eq!(
26522        initial_count, 3,
26523        "Should have 3 cursors after adding 2 below"
26524    );
26525
26526    // Move down one line
26527    cx.update_editor(|editor, window, cx| {
26528        editor.move_down(&MoveDown, window, cx);
26529    });
26530
26531    // Add another cursor below
26532    cx.update_editor(|editor, window, cx| {
26533        editor.add_selection_below(&Default::default(), window, cx);
26534    });
26535
26536    // Should now have 4 cursors (3 original + 1 new)
26537    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26538    assert_eq!(
26539        final_count, 4,
26540        "Should have 4 cursors after moving and adding another"
26541    );
26542}
26543
26544#[gpui::test]
26545async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26546    init_test(cx, |_| {});
26547
26548    let mut cx = EditorTestContext::new(cx).await;
26549
26550    cx.set_state(indoc!(
26551        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26552           Second line here"#
26553    ));
26554
26555    cx.update_editor(|editor, window, cx| {
26556        // Enable soft wrapping with a narrow width to force soft wrapping and
26557        // confirm that more than 2 rows are being displayed.
26558        editor.set_wrap_width(Some(100.0.into()), cx);
26559        assert!(editor.display_text(cx).lines().count() > 2);
26560
26561        editor.add_selection_below(
26562            &AddSelectionBelow {
26563                skip_soft_wrap: true,
26564            },
26565            window,
26566            cx,
26567        );
26568
26569        assert_eq!(
26570            display_ranges(editor, cx),
26571            &[
26572                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26573                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26574            ]
26575        );
26576
26577        editor.add_selection_above(
26578            &AddSelectionAbove {
26579                skip_soft_wrap: true,
26580            },
26581            window,
26582            cx,
26583        );
26584
26585        assert_eq!(
26586            display_ranges(editor, cx),
26587            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26588        );
26589
26590        editor.add_selection_below(
26591            &AddSelectionBelow {
26592                skip_soft_wrap: false,
26593            },
26594            window,
26595            cx,
26596        );
26597
26598        assert_eq!(
26599            display_ranges(editor, cx),
26600            &[
26601                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26602                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26603            ]
26604        );
26605
26606        editor.add_selection_above(
26607            &AddSelectionAbove {
26608                skip_soft_wrap: false,
26609            },
26610            window,
26611            cx,
26612        );
26613
26614        assert_eq!(
26615            display_ranges(editor, cx),
26616            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26617        );
26618    });
26619}
26620
26621#[gpui::test(iterations = 10)]
26622async fn test_document_colors(cx: &mut TestAppContext) {
26623    let expected_color = Rgba {
26624        r: 0.33,
26625        g: 0.33,
26626        b: 0.33,
26627        a: 0.33,
26628    };
26629
26630    init_test(cx, |_| {});
26631
26632    let fs = FakeFs::new(cx.executor());
26633    fs.insert_tree(
26634        path!("/a"),
26635        json!({
26636            "first.rs": "fn main() { let a = 5; }",
26637        }),
26638    )
26639    .await;
26640
26641    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26642    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26643    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26644
26645    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26646    language_registry.add(rust_lang());
26647    let mut fake_servers = language_registry.register_fake_lsp(
26648        "Rust",
26649        FakeLspAdapter {
26650            capabilities: lsp::ServerCapabilities {
26651                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26652                ..lsp::ServerCapabilities::default()
26653            },
26654            name: "rust-analyzer",
26655            ..FakeLspAdapter::default()
26656        },
26657    );
26658    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26659        "Rust",
26660        FakeLspAdapter {
26661            capabilities: lsp::ServerCapabilities {
26662                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26663                ..lsp::ServerCapabilities::default()
26664            },
26665            name: "not-rust-analyzer",
26666            ..FakeLspAdapter::default()
26667        },
26668    );
26669
26670    let editor = workspace
26671        .update(cx, |workspace, window, cx| {
26672            workspace.open_abs_path(
26673                PathBuf::from(path!("/a/first.rs")),
26674                OpenOptions::default(),
26675                window,
26676                cx,
26677            )
26678        })
26679        .unwrap()
26680        .await
26681        .unwrap()
26682        .downcast::<Editor>()
26683        .unwrap();
26684    let fake_language_server = fake_servers.next().await.unwrap();
26685    let fake_language_server_without_capabilities =
26686        fake_servers_without_capabilities.next().await.unwrap();
26687    let requests_made = Arc::new(AtomicUsize::new(0));
26688    let closure_requests_made = Arc::clone(&requests_made);
26689    let mut color_request_handle = fake_language_server
26690        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26691            let requests_made = Arc::clone(&closure_requests_made);
26692            async move {
26693                assert_eq!(
26694                    params.text_document.uri,
26695                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26696                );
26697                requests_made.fetch_add(1, atomic::Ordering::Release);
26698                Ok(vec![
26699                    lsp::ColorInformation {
26700                        range: lsp::Range {
26701                            start: lsp::Position {
26702                                line: 0,
26703                                character: 0,
26704                            },
26705                            end: lsp::Position {
26706                                line: 0,
26707                                character: 1,
26708                            },
26709                        },
26710                        color: lsp::Color {
26711                            red: 0.33,
26712                            green: 0.33,
26713                            blue: 0.33,
26714                            alpha: 0.33,
26715                        },
26716                    },
26717                    lsp::ColorInformation {
26718                        range: lsp::Range {
26719                            start: lsp::Position {
26720                                line: 0,
26721                                character: 0,
26722                            },
26723                            end: lsp::Position {
26724                                line: 0,
26725                                character: 1,
26726                            },
26727                        },
26728                        color: lsp::Color {
26729                            red: 0.33,
26730                            green: 0.33,
26731                            blue: 0.33,
26732                            alpha: 0.33,
26733                        },
26734                    },
26735                ])
26736            }
26737        });
26738
26739    let _handle = fake_language_server_without_capabilities
26740        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26741            panic!("Should not be called");
26742        });
26743    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26744    color_request_handle.next().await.unwrap();
26745    cx.run_until_parked();
26746    assert_eq!(
26747        1,
26748        requests_made.load(atomic::Ordering::Acquire),
26749        "Should query for colors once per editor open"
26750    );
26751    editor.update_in(cx, |editor, _, cx| {
26752        assert_eq!(
26753            vec![expected_color],
26754            extract_color_inlays(editor, cx),
26755            "Should have an initial inlay"
26756        );
26757    });
26758
26759    // opening another file in a split should not influence the LSP query counter
26760    workspace
26761        .update(cx, |workspace, window, cx| {
26762            assert_eq!(
26763                workspace.panes().len(),
26764                1,
26765                "Should have one pane with one editor"
26766            );
26767            workspace.move_item_to_pane_in_direction(
26768                &MoveItemToPaneInDirection {
26769                    direction: SplitDirection::Right,
26770                    focus: false,
26771                    clone: true,
26772                },
26773                window,
26774                cx,
26775            );
26776        })
26777        .unwrap();
26778    cx.run_until_parked();
26779    workspace
26780        .update(cx, |workspace, _, cx| {
26781            let panes = workspace.panes();
26782            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26783            for pane in panes {
26784                let editor = pane
26785                    .read(cx)
26786                    .active_item()
26787                    .and_then(|item| item.downcast::<Editor>())
26788                    .expect("Should have opened an editor in each split");
26789                let editor_file = editor
26790                    .read(cx)
26791                    .buffer()
26792                    .read(cx)
26793                    .as_singleton()
26794                    .expect("test deals with singleton buffers")
26795                    .read(cx)
26796                    .file()
26797                    .expect("test buffese should have a file")
26798                    .path();
26799                assert_eq!(
26800                    editor_file.as_ref(),
26801                    rel_path("first.rs"),
26802                    "Both editors should be opened for the same file"
26803                )
26804            }
26805        })
26806        .unwrap();
26807
26808    cx.executor().advance_clock(Duration::from_millis(500));
26809    let save = editor.update_in(cx, |editor, window, cx| {
26810        editor.move_to_end(&MoveToEnd, window, cx);
26811        editor.handle_input("dirty", window, cx);
26812        editor.save(
26813            SaveOptions {
26814                format: true,
26815                autosave: true,
26816            },
26817            project.clone(),
26818            window,
26819            cx,
26820        )
26821    });
26822    save.await.unwrap();
26823
26824    color_request_handle.next().await.unwrap();
26825    cx.run_until_parked();
26826    assert_eq!(
26827        2,
26828        requests_made.load(atomic::Ordering::Acquire),
26829        "Should query for colors once per save (deduplicated) and once per formatting after save"
26830    );
26831
26832    drop(editor);
26833    let close = workspace
26834        .update(cx, |workspace, window, cx| {
26835            workspace.active_pane().update(cx, |pane, cx| {
26836                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26837            })
26838        })
26839        .unwrap();
26840    close.await.unwrap();
26841    let close = workspace
26842        .update(cx, |workspace, window, cx| {
26843            workspace.active_pane().update(cx, |pane, cx| {
26844                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26845            })
26846        })
26847        .unwrap();
26848    close.await.unwrap();
26849    assert_eq!(
26850        2,
26851        requests_made.load(atomic::Ordering::Acquire),
26852        "After saving and closing all editors, no extra requests should be made"
26853    );
26854    workspace
26855        .update(cx, |workspace, _, cx| {
26856            assert!(
26857                workspace.active_item(cx).is_none(),
26858                "Should close all editors"
26859            )
26860        })
26861        .unwrap();
26862
26863    workspace
26864        .update(cx, |workspace, window, cx| {
26865            workspace.active_pane().update(cx, |pane, cx| {
26866                pane.navigate_backward(&workspace::GoBack, window, cx);
26867            })
26868        })
26869        .unwrap();
26870    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26871    cx.run_until_parked();
26872    let editor = workspace
26873        .update(cx, |workspace, _, cx| {
26874            workspace
26875                .active_item(cx)
26876                .expect("Should have reopened the editor again after navigating back")
26877                .downcast::<Editor>()
26878                .expect("Should be an editor")
26879        })
26880        .unwrap();
26881
26882    assert_eq!(
26883        2,
26884        requests_made.load(atomic::Ordering::Acquire),
26885        "Cache should be reused on buffer close and reopen"
26886    );
26887    editor.update(cx, |editor, cx| {
26888        assert_eq!(
26889            vec![expected_color],
26890            extract_color_inlays(editor, cx),
26891            "Should have an initial inlay"
26892        );
26893    });
26894
26895    drop(color_request_handle);
26896    let closure_requests_made = Arc::clone(&requests_made);
26897    let mut empty_color_request_handle = fake_language_server
26898        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26899            let requests_made = Arc::clone(&closure_requests_made);
26900            async move {
26901                assert_eq!(
26902                    params.text_document.uri,
26903                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26904                );
26905                requests_made.fetch_add(1, atomic::Ordering::Release);
26906                Ok(Vec::new())
26907            }
26908        });
26909    let save = editor.update_in(cx, |editor, window, cx| {
26910        editor.move_to_end(&MoveToEnd, window, cx);
26911        editor.handle_input("dirty_again", window, cx);
26912        editor.save(
26913            SaveOptions {
26914                format: false,
26915                autosave: true,
26916            },
26917            project.clone(),
26918            window,
26919            cx,
26920        )
26921    });
26922    save.await.unwrap();
26923
26924    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26925    empty_color_request_handle.next().await.unwrap();
26926    cx.run_until_parked();
26927    assert_eq!(
26928        3,
26929        requests_made.load(atomic::Ordering::Acquire),
26930        "Should query for colors once per save only, as formatting was not requested"
26931    );
26932    editor.update(cx, |editor, cx| {
26933        assert_eq!(
26934            Vec::<Rgba>::new(),
26935            extract_color_inlays(editor, cx),
26936            "Should clear all colors when the server returns an empty response"
26937        );
26938    });
26939}
26940
26941#[gpui::test]
26942async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26943    init_test(cx, |_| {});
26944    let (editor, cx) = cx.add_window_view(Editor::single_line);
26945    editor.update_in(cx, |editor, window, cx| {
26946        editor.set_text("oops\n\nwow\n", window, cx)
26947    });
26948    cx.run_until_parked();
26949    editor.update(cx, |editor, cx| {
26950        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26951    });
26952    editor.update(cx, |editor, cx| {
26953        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
26954    });
26955    cx.run_until_parked();
26956    editor.update(cx, |editor, cx| {
26957        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26958    });
26959}
26960
26961#[gpui::test]
26962async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26963    init_test(cx, |_| {});
26964
26965    cx.update(|cx| {
26966        register_project_item::<Editor>(cx);
26967    });
26968
26969    let fs = FakeFs::new(cx.executor());
26970    fs.insert_tree("/root1", json!({})).await;
26971    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26972        .await;
26973
26974    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26975    let (workspace, cx) =
26976        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26977
26978    let worktree_id = project.update(cx, |project, cx| {
26979        project.worktrees(cx).next().unwrap().read(cx).id()
26980    });
26981
26982    let handle = workspace
26983        .update_in(cx, |workspace, window, cx| {
26984            let project_path = (worktree_id, rel_path("one.pdf"));
26985            workspace.open_path(project_path, None, true, window, cx)
26986        })
26987        .await
26988        .unwrap();
26989
26990    assert_eq!(
26991        handle.to_any_view().entity_type(),
26992        TypeId::of::<InvalidItemView>()
26993    );
26994}
26995
26996#[gpui::test]
26997async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
26998    init_test(cx, |_| {});
26999
27000    let language = Arc::new(Language::new(
27001        LanguageConfig::default(),
27002        Some(tree_sitter_rust::LANGUAGE.into()),
27003    ));
27004
27005    // Test hierarchical sibling navigation
27006    let text = r#"
27007        fn outer() {
27008            if condition {
27009                let a = 1;
27010            }
27011            let b = 2;
27012        }
27013
27014        fn another() {
27015            let c = 3;
27016        }
27017    "#;
27018
27019    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27020    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27021    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27022
27023    // Wait for parsing to complete
27024    editor
27025        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27026        .await;
27027
27028    editor.update_in(cx, |editor, window, cx| {
27029        // Start by selecting "let a = 1;" inside the if block
27030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27031            s.select_display_ranges([
27032                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27033            ]);
27034        });
27035
27036        let initial_selection = editor
27037            .selections
27038            .display_ranges(&editor.display_snapshot(cx));
27039        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27040
27041        // Test select next sibling - should move up levels to find the next sibling
27042        // Since "let a = 1;" has no siblings in the if block, it should move up
27043        // to find "let b = 2;" which is a sibling of the if block
27044        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27045        let next_selection = editor
27046            .selections
27047            .display_ranges(&editor.display_snapshot(cx));
27048
27049        // Should have a selection and it should be different from the initial
27050        assert_eq!(
27051            next_selection.len(),
27052            1,
27053            "Should have one selection after next"
27054        );
27055        assert_ne!(
27056            next_selection[0], initial_selection[0],
27057            "Next sibling selection should be different"
27058        );
27059
27060        // Test hierarchical navigation by going to the end of the current function
27061        // and trying to navigate to the next function
27062        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27063            s.select_display_ranges([
27064                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27065            ]);
27066        });
27067
27068        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27069        let function_next_selection = editor
27070            .selections
27071            .display_ranges(&editor.display_snapshot(cx));
27072
27073        // Should move to the next function
27074        assert_eq!(
27075            function_next_selection.len(),
27076            1,
27077            "Should have one selection after function next"
27078        );
27079
27080        // Test select previous sibling navigation
27081        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27082        let prev_selection = editor
27083            .selections
27084            .display_ranges(&editor.display_snapshot(cx));
27085
27086        // Should have a selection and it should be different
27087        assert_eq!(
27088            prev_selection.len(),
27089            1,
27090            "Should have one selection after prev"
27091        );
27092        assert_ne!(
27093            prev_selection[0], function_next_selection[0],
27094            "Previous sibling selection should be different from next"
27095        );
27096    });
27097}
27098
27099#[gpui::test]
27100async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27101    init_test(cx, |_| {});
27102
27103    let mut cx = EditorTestContext::new(cx).await;
27104    cx.set_state(
27105        "let ˇvariable = 42;
27106let another = variable + 1;
27107let result = variable * 2;",
27108    );
27109
27110    // Set up document highlights manually (simulating LSP response)
27111    cx.update_editor(|editor, _window, cx| {
27112        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27113
27114        // Create highlights for "variable" occurrences
27115        let highlight_ranges = [
27116            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27117            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27118            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27119        ];
27120
27121        let anchor_ranges: Vec<_> = highlight_ranges
27122            .iter()
27123            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27124            .collect();
27125
27126        editor.highlight_background::<DocumentHighlightRead>(
27127            &anchor_ranges,
27128            |theme| theme.colors().editor_document_highlight_read_background,
27129            cx,
27130        );
27131    });
27132
27133    // Go to next highlight - should move to second "variable"
27134    cx.update_editor(|editor, window, cx| {
27135        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27136    });
27137    cx.assert_editor_state(
27138        "let variable = 42;
27139let another = ˇvariable + 1;
27140let result = variable * 2;",
27141    );
27142
27143    // Go to next highlight - should move to third "variable"
27144    cx.update_editor(|editor, window, cx| {
27145        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27146    });
27147    cx.assert_editor_state(
27148        "let variable = 42;
27149let another = variable + 1;
27150let result = ˇvariable * 2;",
27151    );
27152
27153    // Go to next highlight - should stay at third "variable" (no wrap-around)
27154    cx.update_editor(|editor, window, cx| {
27155        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27156    });
27157    cx.assert_editor_state(
27158        "let variable = 42;
27159let another = variable + 1;
27160let result = ˇvariable * 2;",
27161    );
27162
27163    // Now test going backwards from third position
27164    cx.update_editor(|editor, window, cx| {
27165        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27166    });
27167    cx.assert_editor_state(
27168        "let variable = 42;
27169let another = ˇvariable + 1;
27170let result = variable * 2;",
27171    );
27172
27173    // Go to previous highlight - should move to first "variable"
27174    cx.update_editor(|editor, window, cx| {
27175        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27176    });
27177    cx.assert_editor_state(
27178        "let ˇvariable = 42;
27179let another = variable + 1;
27180let result = variable * 2;",
27181    );
27182
27183    // Go to previous highlight - should stay on first "variable"
27184    cx.update_editor(|editor, window, cx| {
27185        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27186    });
27187    cx.assert_editor_state(
27188        "let ˇvariable = 42;
27189let another = variable + 1;
27190let result = variable * 2;",
27191    );
27192}
27193
27194#[gpui::test]
27195async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27196    cx: &mut gpui::TestAppContext,
27197) {
27198    init_test(cx, |_| {});
27199
27200    let url = "https://zed.dev";
27201
27202    let markdown_language = Arc::new(Language::new(
27203        LanguageConfig {
27204            name: "Markdown".into(),
27205            ..LanguageConfig::default()
27206        },
27207        None,
27208    ));
27209
27210    let mut cx = EditorTestContext::new(cx).await;
27211    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27212    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27213
27214    cx.update_editor(|editor, window, cx| {
27215        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27216        editor.paste(&Paste, window, cx);
27217    });
27218
27219    cx.assert_editor_state(&format!(
27220        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27221    ));
27222}
27223
27224#[gpui::test]
27225async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27226    cx: &mut gpui::TestAppContext,
27227) {
27228    init_test(cx, |_| {});
27229
27230    let url = "https://zed.dev";
27231
27232    let markdown_language = Arc::new(Language::new(
27233        LanguageConfig {
27234            name: "Markdown".into(),
27235            ..LanguageConfig::default()
27236        },
27237        None,
27238    ));
27239
27240    let mut cx = EditorTestContext::new(cx).await;
27241    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27242    cx.set_state(&format!(
27243        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27244    ));
27245
27246    cx.update_editor(|editor, window, cx| {
27247        editor.copy(&Copy, window, cx);
27248    });
27249
27250    cx.set_state(&format!(
27251        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27252    ));
27253
27254    cx.update_editor(|editor, window, cx| {
27255        editor.paste(&Paste, window, cx);
27256    });
27257
27258    cx.assert_editor_state(&format!(
27259        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27260    ));
27261}
27262
27263#[gpui::test]
27264async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27265    cx: &mut gpui::TestAppContext,
27266) {
27267    init_test(cx, |_| {});
27268
27269    let url = "https://zed.dev";
27270
27271    let markdown_language = Arc::new(Language::new(
27272        LanguageConfig {
27273            name: "Markdown".into(),
27274            ..LanguageConfig::default()
27275        },
27276        None,
27277    ));
27278
27279    let mut cx = EditorTestContext::new(cx).await;
27280    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27281    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27282
27283    cx.update_editor(|editor, window, cx| {
27284        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27285        editor.paste(&Paste, window, cx);
27286    });
27287
27288    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27289}
27290
27291#[gpui::test]
27292async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27293    cx: &mut gpui::TestAppContext,
27294) {
27295    init_test(cx, |_| {});
27296
27297    let text = "Awesome";
27298
27299    let markdown_language = Arc::new(Language::new(
27300        LanguageConfig {
27301            name: "Markdown".into(),
27302            ..LanguageConfig::default()
27303        },
27304        None,
27305    ));
27306
27307    let mut cx = EditorTestContext::new(cx).await;
27308    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27309    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27310
27311    cx.update_editor(|editor, window, cx| {
27312        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27313        editor.paste(&Paste, window, cx);
27314    });
27315
27316    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27317}
27318
27319#[gpui::test]
27320async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27321    cx: &mut gpui::TestAppContext,
27322) {
27323    init_test(cx, |_| {});
27324
27325    let url = "https://zed.dev";
27326
27327    let markdown_language = Arc::new(Language::new(
27328        LanguageConfig {
27329            name: "Rust".into(),
27330            ..LanguageConfig::default()
27331        },
27332        None,
27333    ));
27334
27335    let mut cx = EditorTestContext::new(cx).await;
27336    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27337    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27338
27339    cx.update_editor(|editor, window, cx| {
27340        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27341        editor.paste(&Paste, window, cx);
27342    });
27343
27344    cx.assert_editor_state(&format!(
27345        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27346    ));
27347}
27348
27349#[gpui::test]
27350async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27351    cx: &mut TestAppContext,
27352) {
27353    init_test(cx, |_| {});
27354
27355    let url = "https://zed.dev";
27356
27357    let markdown_language = Arc::new(Language::new(
27358        LanguageConfig {
27359            name: "Markdown".into(),
27360            ..LanguageConfig::default()
27361        },
27362        None,
27363    ));
27364
27365    let (editor, cx) = cx.add_window_view(|window, cx| {
27366        let multi_buffer = MultiBuffer::build_multi(
27367            [
27368                ("this will embed -> link", vec![Point::row_range(0..1)]),
27369                ("this will replace -> link", vec![Point::row_range(0..1)]),
27370            ],
27371            cx,
27372        );
27373        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
27374        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27375            s.select_ranges(vec![
27376                Point::new(0, 19)..Point::new(0, 23),
27377                Point::new(1, 21)..Point::new(1, 25),
27378            ])
27379        });
27380        let first_buffer_id = multi_buffer
27381            .read(cx)
27382            .excerpt_buffer_ids()
27383            .into_iter()
27384            .next()
27385            .unwrap();
27386        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
27387        first_buffer.update(cx, |buffer, cx| {
27388            buffer.set_language(Some(markdown_language.clone()), cx);
27389        });
27390
27391        editor
27392    });
27393    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27394
27395    cx.update_editor(|editor, window, cx| {
27396        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27397        editor.paste(&Paste, window, cx);
27398    });
27399
27400    cx.assert_editor_state(&format!(
27401        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
27402    ));
27403}
27404
27405#[gpui::test]
27406async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
27407    init_test(cx, |_| {});
27408
27409    let fs = FakeFs::new(cx.executor());
27410    fs.insert_tree(
27411        path!("/project"),
27412        json!({
27413            "first.rs": "# First Document\nSome content here.",
27414            "second.rs": "Plain text content for second file.",
27415        }),
27416    )
27417    .await;
27418
27419    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
27420    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27421    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27422
27423    let language = rust_lang();
27424    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27425    language_registry.add(language.clone());
27426    let mut fake_servers = language_registry.register_fake_lsp(
27427        "Rust",
27428        FakeLspAdapter {
27429            ..FakeLspAdapter::default()
27430        },
27431    );
27432
27433    let buffer1 = project
27434        .update(cx, |project, cx| {
27435            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
27436        })
27437        .await
27438        .unwrap();
27439    let buffer2 = project
27440        .update(cx, |project, cx| {
27441            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
27442        })
27443        .await
27444        .unwrap();
27445
27446    let multi_buffer = cx.new(|cx| {
27447        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
27448        multi_buffer.set_excerpts_for_path(
27449            PathKey::for_buffer(&buffer1, cx),
27450            buffer1.clone(),
27451            [Point::zero()..buffer1.read(cx).max_point()],
27452            3,
27453            cx,
27454        );
27455        multi_buffer.set_excerpts_for_path(
27456            PathKey::for_buffer(&buffer2, cx),
27457            buffer2.clone(),
27458            [Point::zero()..buffer1.read(cx).max_point()],
27459            3,
27460            cx,
27461        );
27462        multi_buffer
27463    });
27464
27465    let (editor, cx) = cx.add_window_view(|window, cx| {
27466        Editor::new(
27467            EditorMode::full(),
27468            multi_buffer,
27469            Some(project.clone()),
27470            window,
27471            cx,
27472        )
27473    });
27474
27475    let fake_language_server = fake_servers.next().await.unwrap();
27476
27477    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
27478
27479    let save = editor.update_in(cx, |editor, window, cx| {
27480        assert!(editor.is_dirty(cx));
27481
27482        editor.save(
27483            SaveOptions {
27484                format: true,
27485                autosave: true,
27486            },
27487            project,
27488            window,
27489            cx,
27490        )
27491    });
27492    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27493    let (done_edit_tx, done_edit_rx) = oneshot::channel();
27494    let mut done_edit_rx = Some(done_edit_rx);
27495    let mut start_edit_tx = Some(start_edit_tx);
27496
27497    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
27498        start_edit_tx.take().unwrap().send(()).unwrap();
27499        let done_edit_rx = done_edit_rx.take().unwrap();
27500        async move {
27501            done_edit_rx.await.unwrap();
27502            Ok(None)
27503        }
27504    });
27505
27506    start_edit_rx.await.unwrap();
27507    buffer2
27508        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
27509        .unwrap();
27510
27511    done_edit_tx.send(()).unwrap();
27512
27513    save.await.unwrap();
27514    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
27515}
27516
27517#[track_caller]
27518fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
27519    editor
27520        .all_inlays(cx)
27521        .into_iter()
27522        .filter_map(|inlay| inlay.get_color())
27523        .map(Rgba::from)
27524        .collect()
27525}
27526
27527#[gpui::test]
27528fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
27529    init_test(cx, |_| {});
27530
27531    let editor = cx.add_window(|window, cx| {
27532        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
27533        build_editor(buffer, window, cx)
27534    });
27535
27536    editor
27537        .update(cx, |editor, window, cx| {
27538            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27539                s.select_display_ranges([
27540                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
27541                ])
27542            });
27543
27544            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
27545
27546            assert_eq!(
27547                editor.display_text(cx),
27548                "line1\nline2\nline2",
27549                "Duplicating last line upward should create duplicate above, not on same line"
27550            );
27551
27552            assert_eq!(
27553                editor
27554                    .selections
27555                    .display_ranges(&editor.display_snapshot(cx)),
27556                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27557                "Selection should move to the duplicated line"
27558            );
27559        })
27560        .unwrap();
27561}
27562
27563#[gpui::test]
27564async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27565    init_test(cx, |_| {});
27566
27567    let mut cx = EditorTestContext::new(cx).await;
27568
27569    cx.set_state("line1\nline2ˇ");
27570
27571    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27572
27573    let clipboard_text = cx
27574        .read_from_clipboard()
27575        .and_then(|item| item.text().as_deref().map(str::to_string));
27576
27577    assert_eq!(
27578        clipboard_text,
27579        Some("line2\n".to_string()),
27580        "Copying a line without trailing newline should include a newline"
27581    );
27582
27583    cx.set_state("line1\nˇ");
27584
27585    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27586
27587    cx.assert_editor_state("line1\nline2\nˇ");
27588}
27589
27590#[gpui::test]
27591async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27592    init_test(cx, |_| {});
27593
27594    let mut cx = EditorTestContext::new(cx).await;
27595
27596    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27597
27598    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27599
27600    let clipboard_text = cx
27601        .read_from_clipboard()
27602        .and_then(|item| item.text().as_deref().map(str::to_string));
27603
27604    assert_eq!(
27605        clipboard_text,
27606        Some("line1\nline2\nline3\n".to_string()),
27607        "Copying multiple lines should include a single newline between lines"
27608    );
27609
27610    cx.set_state("lineA\nˇ");
27611
27612    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27613
27614    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27615}
27616
27617#[gpui::test]
27618async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27619    init_test(cx, |_| {});
27620
27621    let mut cx = EditorTestContext::new(cx).await;
27622
27623    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27624
27625    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
27626
27627    let clipboard_text = cx
27628        .read_from_clipboard()
27629        .and_then(|item| item.text().as_deref().map(str::to_string));
27630
27631    assert_eq!(
27632        clipboard_text,
27633        Some("line1\nline2\nline3\n".to_string()),
27634        "Copying multiple lines should include a single newline between lines"
27635    );
27636
27637    cx.set_state("lineA\nˇ");
27638
27639    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27640
27641    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27642}
27643
27644#[gpui::test]
27645async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27646    init_test(cx, |_| {});
27647
27648    let mut cx = EditorTestContext::new(cx).await;
27649
27650    cx.set_state("line1\nline2ˇ");
27651    cx.update_editor(|e, window, cx| {
27652        e.set_mode(EditorMode::SingleLine);
27653        assert!(e.key_context(window, cx).contains("end_of_input"));
27654    });
27655    cx.set_state("ˇline1\nline2");
27656    cx.update_editor(|e, window, cx| {
27657        assert!(!e.key_context(window, cx).contains("end_of_input"));
27658    });
27659    cx.set_state("line1ˇ\nline2");
27660    cx.update_editor(|e, window, cx| {
27661        assert!(!e.key_context(window, cx).contains("end_of_input"));
27662    });
27663}
27664
27665#[gpui::test]
27666async fn test_sticky_scroll(cx: &mut TestAppContext) {
27667    init_test(cx, |_| {});
27668    let mut cx = EditorTestContext::new(cx).await;
27669
27670    let buffer = indoc! {"
27671            ˇfn foo() {
27672                let abc = 123;
27673            }
27674            struct Bar;
27675            impl Bar {
27676                fn new() -> Self {
27677                    Self
27678                }
27679            }
27680            fn baz() {
27681            }
27682        "};
27683    cx.set_state(&buffer);
27684
27685    cx.update_editor(|e, _, cx| {
27686        e.buffer()
27687            .read(cx)
27688            .as_singleton()
27689            .unwrap()
27690            .update(cx, |buffer, cx| {
27691                buffer.set_language(Some(rust_lang()), cx);
27692            })
27693    });
27694
27695    let mut sticky_headers = |offset: ScrollOffset| {
27696        cx.update_editor(|e, window, cx| {
27697            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27698            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27699                .into_iter()
27700                .map(
27701                    |StickyHeader {
27702                         start_point,
27703                         offset,
27704                         ..
27705                     }| { (start_point, offset) },
27706                )
27707                .collect::<Vec<_>>()
27708        })
27709    };
27710
27711    let fn_foo = Point { row: 0, column: 0 };
27712    let impl_bar = Point { row: 4, column: 0 };
27713    let fn_new = Point { row: 5, column: 4 };
27714
27715    assert_eq!(sticky_headers(0.0), vec![]);
27716    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27717    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27718    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27719    assert_eq!(sticky_headers(2.0), vec![]);
27720    assert_eq!(sticky_headers(2.5), vec![]);
27721    assert_eq!(sticky_headers(3.0), vec![]);
27722    assert_eq!(sticky_headers(3.5), vec![]);
27723    assert_eq!(sticky_headers(4.0), vec![]);
27724    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27725    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27726    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27727    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27728    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27729    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27730    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27731    assert_eq!(sticky_headers(8.0), vec![]);
27732    assert_eq!(sticky_headers(8.5), vec![]);
27733    assert_eq!(sticky_headers(9.0), vec![]);
27734    assert_eq!(sticky_headers(9.5), vec![]);
27735    assert_eq!(sticky_headers(10.0), vec![]);
27736}
27737
27738#[gpui::test]
27739async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27740    init_test(cx, |_| {});
27741    cx.update(|cx| {
27742        SettingsStore::update_global(cx, |store, cx| {
27743            store.update_user_settings(cx, |settings| {
27744                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27745                    enabled: Some(true),
27746                })
27747            });
27748        });
27749    });
27750    let mut cx = EditorTestContext::new(cx).await;
27751
27752    let line_height = cx.editor(|editor, window, _cx| {
27753        editor
27754            .style()
27755            .unwrap()
27756            .text
27757            .line_height_in_pixels(window.rem_size())
27758    });
27759
27760    let buffer = indoc! {"
27761            ˇfn foo() {
27762                let abc = 123;
27763            }
27764            struct Bar;
27765            impl Bar {
27766                fn new() -> Self {
27767                    Self
27768                }
27769            }
27770            fn baz() {
27771            }
27772        "};
27773    cx.set_state(&buffer);
27774
27775    cx.update_editor(|e, _, cx| {
27776        e.buffer()
27777            .read(cx)
27778            .as_singleton()
27779            .unwrap()
27780            .update(cx, |buffer, cx| {
27781                buffer.set_language(Some(rust_lang()), cx);
27782            })
27783    });
27784
27785    let fn_foo = || empty_range(0, 0);
27786    let impl_bar = || empty_range(4, 0);
27787    let fn_new = || empty_range(5, 4);
27788
27789    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27790        cx.update_editor(|e, window, cx| {
27791            e.scroll(
27792                gpui::Point {
27793                    x: 0.,
27794                    y: scroll_offset,
27795                },
27796                None,
27797                window,
27798                cx,
27799            );
27800        });
27801        cx.simulate_click(
27802            gpui::Point {
27803                x: px(0.),
27804                y: click_offset as f32 * line_height,
27805            },
27806            Modifiers::none(),
27807        );
27808        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27809    };
27810
27811    assert_eq!(
27812        scroll_and_click(
27813            4.5, // impl Bar is halfway off the screen
27814            0.0  // click top of screen
27815        ),
27816        // scrolled to impl Bar
27817        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27818    );
27819
27820    assert_eq!(
27821        scroll_and_click(
27822            4.5,  // impl Bar is halfway off the screen
27823            0.25  // click middle of impl Bar
27824        ),
27825        // scrolled to impl Bar
27826        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27827    );
27828
27829    assert_eq!(
27830        scroll_and_click(
27831            4.5, // impl Bar is halfway off the screen
27832            1.5  // click below impl Bar (e.g. fn new())
27833        ),
27834        // scrolled to fn new() - this is below the impl Bar header which has persisted
27835        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27836    );
27837
27838    assert_eq!(
27839        scroll_and_click(
27840            5.5,  // fn new is halfway underneath impl Bar
27841            0.75  // click on the overlap of impl Bar and fn new()
27842        ),
27843        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27844    );
27845
27846    assert_eq!(
27847        scroll_and_click(
27848            5.5,  // fn new is halfway underneath impl Bar
27849            1.25  // click on the visible part of fn new()
27850        ),
27851        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27852    );
27853
27854    assert_eq!(
27855        scroll_and_click(
27856            1.5, // fn foo is halfway off the screen
27857            0.0  // click top of screen
27858        ),
27859        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
27860    );
27861
27862    assert_eq!(
27863        scroll_and_click(
27864            1.5,  // fn foo is halfway off the screen
27865            0.75  // click visible part of let abc...
27866        )
27867        .0,
27868        // no change in scroll
27869        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
27870        (gpui::Point { x: 0., y: 1.5 })
27871    );
27872}
27873
27874#[gpui::test]
27875async fn test_next_prev_reference(cx: &mut TestAppContext) {
27876    const CYCLE_POSITIONS: &[&'static str] = &[
27877        indoc! {"
27878            fn foo() {
27879                let ˇabc = 123;
27880                let x = abc + 1;
27881                let y = abc + 2;
27882                let z = abc + 2;
27883            }
27884        "},
27885        indoc! {"
27886            fn foo() {
27887                let abc = 123;
27888                let x = ˇabc + 1;
27889                let y = abc + 2;
27890                let z = abc + 2;
27891            }
27892        "},
27893        indoc! {"
27894            fn foo() {
27895                let abc = 123;
27896                let x = abc + 1;
27897                let y = ˇabc + 2;
27898                let z = abc + 2;
27899            }
27900        "},
27901        indoc! {"
27902            fn foo() {
27903                let abc = 123;
27904                let x = abc + 1;
27905                let y = abc + 2;
27906                let z = ˇabc + 2;
27907            }
27908        "},
27909    ];
27910
27911    init_test(cx, |_| {});
27912
27913    let mut cx = EditorLspTestContext::new_rust(
27914        lsp::ServerCapabilities {
27915            references_provider: Some(lsp::OneOf::Left(true)),
27916            ..Default::default()
27917        },
27918        cx,
27919    )
27920    .await;
27921
27922    // importantly, the cursor is in the middle
27923    cx.set_state(indoc! {"
27924        fn foo() {
27925            let aˇbc = 123;
27926            let x = abc + 1;
27927            let y = abc + 2;
27928            let z = abc + 2;
27929        }
27930    "});
27931
27932    let reference_ranges = [
27933        lsp::Position::new(1, 8),
27934        lsp::Position::new(2, 12),
27935        lsp::Position::new(3, 12),
27936        lsp::Position::new(4, 12),
27937    ]
27938    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27939
27940    cx.lsp
27941        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27942            Ok(Some(
27943                reference_ranges
27944                    .map(|range| lsp::Location {
27945                        uri: params.text_document_position.text_document.uri.clone(),
27946                        range,
27947                    })
27948                    .to_vec(),
27949            ))
27950        });
27951
27952    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27953        cx.update_editor(|editor, window, cx| {
27954            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27955        })
27956        .unwrap()
27957        .await
27958        .unwrap()
27959    };
27960
27961    _move(Direction::Next, 1, &mut cx).await;
27962    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27963
27964    _move(Direction::Next, 1, &mut cx).await;
27965    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27966
27967    _move(Direction::Next, 1, &mut cx).await;
27968    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27969
27970    // loops back to the start
27971    _move(Direction::Next, 1, &mut cx).await;
27972    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27973
27974    // loops back to the end
27975    _move(Direction::Prev, 1, &mut cx).await;
27976    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27977
27978    _move(Direction::Prev, 1, &mut cx).await;
27979    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27980
27981    _move(Direction::Prev, 1, &mut cx).await;
27982    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27983
27984    _move(Direction::Prev, 1, &mut cx).await;
27985    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27986
27987    _move(Direction::Next, 3, &mut cx).await;
27988    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27989
27990    _move(Direction::Prev, 2, &mut cx).await;
27991    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27992}
27993
27994#[gpui::test]
27995async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
27996    init_test(cx, |_| {});
27997
27998    let (editor, cx) = cx.add_window_view(|window, cx| {
27999        let multi_buffer = MultiBuffer::build_multi(
28000            [
28001                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28002                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28003            ],
28004            cx,
28005        );
28006        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28007    });
28008
28009    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28010    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28011
28012    cx.assert_excerpts_with_selections(indoc! {"
28013        [EXCERPT]
28014        ˇ1
28015        2
28016        3
28017        [EXCERPT]
28018        1
28019        2
28020        3
28021        "});
28022
28023    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28024    cx.update_editor(|editor, window, cx| {
28025        editor.change_selections(None.into(), window, cx, |s| {
28026            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28027        });
28028    });
28029    cx.assert_excerpts_with_selections(indoc! {"
28030        [EXCERPT]
28031        1
2803228033        3
28034        [EXCERPT]
28035        1
28036        2
28037        3
28038        "});
28039
28040    cx.update_editor(|editor, window, cx| {
28041        editor
28042            .select_all_matches(&SelectAllMatches, window, cx)
28043            .unwrap();
28044    });
28045    cx.assert_excerpts_with_selections(indoc! {"
28046        [EXCERPT]
28047        1
2804828049        3
28050        [EXCERPT]
28051        1
2805228053        3
28054        "});
28055
28056    cx.update_editor(|editor, window, cx| {
28057        editor.handle_input("X", window, cx);
28058    });
28059    cx.assert_excerpts_with_selections(indoc! {"
28060        [EXCERPT]
28061        1
2806228063        3
28064        [EXCERPT]
28065        1
2806628067        3
28068        "});
28069
28070    // Scenario 2: Select "2", then fold second buffer before insertion
28071    cx.update_multibuffer(|mb, cx| {
28072        for buffer_id in buffer_ids.iter() {
28073            let buffer = mb.buffer(*buffer_id).unwrap();
28074            buffer.update(cx, |buffer, cx| {
28075                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28076            });
28077        }
28078    });
28079
28080    // Select "2" and select all matches
28081    cx.update_editor(|editor, window, cx| {
28082        editor.change_selections(None.into(), window, cx, |s| {
28083            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28084        });
28085        editor
28086            .select_all_matches(&SelectAllMatches, window, cx)
28087            .unwrap();
28088    });
28089
28090    // Fold second buffer - should remove selections from folded buffer
28091    cx.update_editor(|editor, _, cx| {
28092        editor.fold_buffer(buffer_ids[1], cx);
28093    });
28094    cx.assert_excerpts_with_selections(indoc! {"
28095        [EXCERPT]
28096        1
2809728098        3
28099        [EXCERPT]
28100        [FOLDED]
28101        "});
28102
28103    // Insert text - should only affect first buffer
28104    cx.update_editor(|editor, window, cx| {
28105        editor.handle_input("Y", window, cx);
28106    });
28107    cx.update_editor(|editor, _, cx| {
28108        editor.unfold_buffer(buffer_ids[1], cx);
28109    });
28110    cx.assert_excerpts_with_selections(indoc! {"
28111        [EXCERPT]
28112        1
2811328114        3
28115        [EXCERPT]
28116        1
28117        2
28118        3
28119        "});
28120
28121    // Scenario 3: Select "2", then fold first buffer before insertion
28122    cx.update_multibuffer(|mb, cx| {
28123        for buffer_id in buffer_ids.iter() {
28124            let buffer = mb.buffer(*buffer_id).unwrap();
28125            buffer.update(cx, |buffer, cx| {
28126                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28127            });
28128        }
28129    });
28130
28131    // Select "2" and select all matches
28132    cx.update_editor(|editor, window, cx| {
28133        editor.change_selections(None.into(), window, cx, |s| {
28134            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28135        });
28136        editor
28137            .select_all_matches(&SelectAllMatches, window, cx)
28138            .unwrap();
28139    });
28140
28141    // Fold first buffer - should remove selections from folded buffer
28142    cx.update_editor(|editor, _, cx| {
28143        editor.fold_buffer(buffer_ids[0], cx);
28144    });
28145    cx.assert_excerpts_with_selections(indoc! {"
28146        [EXCERPT]
28147        [FOLDED]
28148        [EXCERPT]
28149        1
2815028151        3
28152        "});
28153
28154    // Insert text - should only affect second buffer
28155    cx.update_editor(|editor, window, cx| {
28156        editor.handle_input("Z", window, cx);
28157    });
28158    cx.update_editor(|editor, _, cx| {
28159        editor.unfold_buffer(buffer_ids[0], cx);
28160    });
28161    cx.assert_excerpts_with_selections(indoc! {"
28162        [EXCERPT]
28163        1
28164        2
28165        3
28166        [EXCERPT]
28167        1
2816828169        3
28170        "});
28171
28172    // Edge case scenario: fold all buffers, then try to insert
28173    cx.update_editor(|editor, _, cx| {
28174        editor.fold_buffer(buffer_ids[0], cx);
28175        editor.fold_buffer(buffer_ids[1], cx);
28176    });
28177    cx.assert_excerpts_with_selections(indoc! {"
28178        [EXCERPT]
28179        ˇ[FOLDED]
28180        [EXCERPT]
28181        [FOLDED]
28182        "});
28183
28184    // Insert should work via default selection
28185    cx.update_editor(|editor, window, cx| {
28186        editor.handle_input("W", window, cx);
28187    });
28188    cx.update_editor(|editor, _, cx| {
28189        editor.unfold_buffer(buffer_ids[0], cx);
28190        editor.unfold_buffer(buffer_ids[1], cx);
28191    });
28192    cx.assert_excerpts_with_selections(indoc! {"
28193        [EXCERPT]
28194        Wˇ1
28195        2
28196        3
28197        [EXCERPT]
28198        1
28199        Z
28200        3
28201        "});
28202}
28203
28204#[gpui::test]
28205async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
28206    init_test(cx, |_| {});
28207    let mut leader_cx = EditorTestContext::new(cx).await;
28208
28209    let diff_base = indoc!(
28210        r#"
28211        one
28212        two
28213        three
28214        four
28215        five
28216        six
28217        "#
28218    );
28219
28220    let initial_state = indoc!(
28221        r#"
28222        ˇone
28223        two
28224        THREE
28225        four
28226        five
28227        six
28228        "#
28229    );
28230
28231    leader_cx.set_state(initial_state);
28232
28233    leader_cx.set_head_text(&diff_base);
28234    leader_cx.run_until_parked();
28235
28236    let follower = leader_cx.update_multibuffer(|leader, cx| {
28237        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28238        leader.set_all_diff_hunks_expanded(cx);
28239        leader.get_or_create_follower(cx)
28240    });
28241    follower.update(cx, |follower, cx| {
28242        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28243        follower.set_all_diff_hunks_expanded(cx);
28244    });
28245
28246    let follower_editor =
28247        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
28248    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
28249
28250    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
28251    cx.run_until_parked();
28252
28253    leader_cx.assert_editor_state(initial_state);
28254    follower_cx.assert_editor_state(indoc! {
28255        r#"
28256        ˇone
28257        two
28258        three
28259        four
28260        five
28261        six
28262        "#
28263    });
28264
28265    follower_cx.editor(|editor, _window, cx| {
28266        assert!(editor.read_only(cx));
28267    });
28268
28269    leader_cx.update_editor(|editor, _window, cx| {
28270        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
28271    });
28272    cx.run_until_parked();
28273
28274    leader_cx.assert_editor_state(indoc! {
28275        r#"
28276        ˇone
28277        two
28278        THREE
28279        four
28280        FIVE
28281        six
28282        "#
28283    });
28284
28285    follower_cx.assert_editor_state(indoc! {
28286        r#"
28287        ˇone
28288        two
28289        three
28290        four
28291        five
28292        six
28293        "#
28294    });
28295
28296    leader_cx.update_editor(|editor, _window, cx| {
28297        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
28298    });
28299    cx.run_until_parked();
28300
28301    leader_cx.assert_editor_state(indoc! {
28302        r#"
28303        ˇone
28304        two
28305        THREE
28306        four
28307        FIVE
28308        six
28309        SEVEN"#
28310    });
28311
28312    follower_cx.assert_editor_state(indoc! {
28313        r#"
28314        ˇone
28315        two
28316        three
28317        four
28318        five
28319        six
28320        "#
28321    });
28322
28323    leader_cx.update_editor(|editor, window, cx| {
28324        editor.move_down(&MoveDown, window, cx);
28325        editor.refresh_selected_text_highlights(true, window, cx);
28326    });
28327    leader_cx.run_until_parked();
28328}
28329
28330#[gpui::test]
28331async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
28332    init_test(cx, |_| {});
28333    let base_text = "base\n";
28334    let buffer_text = "buffer\n";
28335
28336    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
28337    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
28338
28339    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
28340    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
28341    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
28342    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
28343
28344    let leader = cx.new(|cx| {
28345        let mut leader = MultiBuffer::new(Capability::ReadWrite);
28346        leader.set_all_diff_hunks_expanded(cx);
28347        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28348        leader
28349    });
28350    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
28351    follower.update(cx, |follower, _| {
28352        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28353    });
28354
28355    leader.update(cx, |leader, cx| {
28356        leader.insert_excerpts_after(
28357            ExcerptId::min(),
28358            extra_buffer_2.clone(),
28359            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28360            cx,
28361        );
28362        leader.add_diff(extra_diff_2.clone(), cx);
28363
28364        leader.insert_excerpts_after(
28365            ExcerptId::min(),
28366            extra_buffer_1.clone(),
28367            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28368            cx,
28369        );
28370        leader.add_diff(extra_diff_1.clone(), cx);
28371
28372        leader.insert_excerpts_after(
28373            ExcerptId::min(),
28374            buffer1.clone(),
28375            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28376            cx,
28377        );
28378        leader.add_diff(diff1.clone(), cx);
28379    });
28380
28381    cx.run_until_parked();
28382    let mut cx = cx.add_empty_window();
28383
28384    let leader_editor = cx
28385        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
28386    let follower_editor = cx.new_window_entity(|window, cx| {
28387        Editor::for_multibuffer(follower.clone(), None, window, cx)
28388    });
28389
28390    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
28391    leader_cx.assert_editor_state(indoc! {"
28392       ˇbuffer
28393
28394       dummy text 1
28395
28396       dummy text 2
28397    "});
28398    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
28399    follower_cx.assert_editor_state(indoc! {"
28400        ˇbase
28401
28402
28403    "});
28404}
28405
28406#[gpui::test]
28407async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
28408    init_test(cx, |_| {});
28409
28410    let (editor, cx) = cx.add_window_view(|window, cx| {
28411        let multi_buffer = MultiBuffer::build_multi(
28412            [
28413                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28414                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
28415            ],
28416            cx,
28417        );
28418        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28419    });
28420
28421    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28422
28423    cx.assert_excerpts_with_selections(indoc! {"
28424        [EXCERPT]
28425        ˇ1
28426        2
28427        3
28428        [EXCERPT]
28429        1
28430        2
28431        3
28432        4
28433        5
28434        6
28435        7
28436        8
28437        9
28438        "});
28439
28440    cx.update_editor(|editor, window, cx| {
28441        editor.change_selections(None.into(), window, cx, |s| {
28442            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
28443        });
28444    });
28445
28446    cx.assert_excerpts_with_selections(indoc! {"
28447        [EXCERPT]
28448        1
28449        2
28450        3
28451        [EXCERPT]
28452        1
28453        2
28454        3
28455        4
28456        5
28457        6
28458        ˇ7
28459        8
28460        9
28461        "});
28462
28463    cx.update_editor(|editor, _window, cx| {
28464        editor.set_vertical_scroll_margin(0, cx);
28465    });
28466
28467    cx.update_editor(|editor, window, cx| {
28468        assert_eq!(editor.vertical_scroll_margin(), 0);
28469        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28470        assert_eq!(
28471            editor.snapshot(window, cx).scroll_position(),
28472            gpui::Point::new(0., 12.0)
28473        );
28474    });
28475
28476    cx.update_editor(|editor, _window, cx| {
28477        editor.set_vertical_scroll_margin(3, cx);
28478    });
28479
28480    cx.update_editor(|editor, window, cx| {
28481        assert_eq!(editor.vertical_scroll_margin(), 3);
28482        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28483        assert_eq!(
28484            editor.snapshot(window, cx).scroll_position(),
28485            gpui::Point::new(0., 9.0)
28486        );
28487    });
28488}