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_can_run_commands(cx: &mut TestAppContext) {
14760    init_test(cx, |_| {});
14761
14762    let fs = FakeFs::new(cx.executor());
14763    fs.insert_tree(
14764        path!("/a"),
14765        json!({
14766            "main.rs": "",
14767        }),
14768    )
14769    .await;
14770
14771    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14772    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14773    language_registry.add(rust_lang());
14774    let command_calls = Arc::new(AtomicUsize::new(0));
14775    let registered_command = "_the/command";
14776
14777    let closure_command_calls = command_calls.clone();
14778    let mut fake_servers = language_registry.register_fake_lsp(
14779        "Rust",
14780        FakeLspAdapter {
14781            capabilities: lsp::ServerCapabilities {
14782                completion_provider: Some(lsp::CompletionOptions {
14783                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14784                    ..lsp::CompletionOptions::default()
14785                }),
14786                execute_command_provider: Some(lsp::ExecuteCommandOptions {
14787                    commands: vec![registered_command.to_owned()],
14788                    ..lsp::ExecuteCommandOptions::default()
14789                }),
14790                ..lsp::ServerCapabilities::default()
14791            },
14792            initializer: Some(Box::new(move |fake_server| {
14793                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14794                    move |params, _| async move {
14795                        Ok(Some(lsp::CompletionResponse::Array(vec![
14796                            lsp::CompletionItem {
14797                                label: "registered_command".to_owned(),
14798                                text_edit: gen_text_edit(&params, ""),
14799                                command: Some(lsp::Command {
14800                                    title: registered_command.to_owned(),
14801                                    command: "_the/command".to_owned(),
14802                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
14803                                }),
14804                                ..lsp::CompletionItem::default()
14805                            },
14806                            lsp::CompletionItem {
14807                                label: "unregistered_command".to_owned(),
14808                                text_edit: gen_text_edit(&params, ""),
14809                                command: Some(lsp::Command {
14810                                    title: "????????????".to_owned(),
14811                                    command: "????????????".to_owned(),
14812                                    arguments: Some(vec![serde_json::Value::Null]),
14813                                }),
14814                                ..lsp::CompletionItem::default()
14815                            },
14816                        ])))
14817                    },
14818                );
14819                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
14820                    let command_calls = closure_command_calls.clone();
14821                    move |params, _| {
14822                        assert_eq!(params.command, registered_command);
14823                        let command_calls = command_calls.clone();
14824                        async move {
14825                            command_calls.fetch_add(1, atomic::Ordering::Release);
14826                            Ok(Some(json!(null)))
14827                        }
14828                    }
14829                });
14830            })),
14831            ..FakeLspAdapter::default()
14832        },
14833    );
14834    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14835    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14836    let editor = workspace
14837        .update(cx, |workspace, window, cx| {
14838            workspace.open_abs_path(
14839                PathBuf::from(path!("/a/main.rs")),
14840                OpenOptions::default(),
14841                window,
14842                cx,
14843            )
14844        })
14845        .unwrap()
14846        .await
14847        .unwrap()
14848        .downcast::<Editor>()
14849        .unwrap();
14850    let _fake_server = fake_servers.next().await.unwrap();
14851
14852    editor.update_in(cx, |editor, window, cx| {
14853        cx.focus_self(window);
14854        editor.move_to_end(&MoveToEnd, window, cx);
14855        editor.handle_input(".", window, cx);
14856    });
14857    cx.run_until_parked();
14858    editor.update(cx, |editor, _| {
14859        assert!(editor.context_menu_visible());
14860        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14861        {
14862            let completion_labels = menu
14863                .completions
14864                .borrow()
14865                .iter()
14866                .map(|c| c.label.text.clone())
14867                .collect::<Vec<_>>();
14868            assert_eq!(
14869                completion_labels,
14870                &["registered_command", "unregistered_command",],
14871            );
14872        } else {
14873            panic!("expected completion menu to be open");
14874        }
14875    });
14876
14877    editor
14878        .update_in(cx, |editor, window, cx| {
14879            editor
14880                .confirm_completion(&ConfirmCompletion::default(), window, cx)
14881                .unwrap()
14882        })
14883        .await
14884        .unwrap();
14885    cx.run_until_parked();
14886    assert_eq!(
14887        command_calls.load(atomic::Ordering::Acquire),
14888        1,
14889        "For completion with a registered command, Zed should send a command execution request",
14890    );
14891
14892    editor.update_in(cx, |editor, window, cx| {
14893        cx.focus_self(window);
14894        editor.handle_input(".", window, cx);
14895    });
14896    cx.run_until_parked();
14897    editor.update(cx, |editor, _| {
14898        assert!(editor.context_menu_visible());
14899        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14900        {
14901            let completion_labels = menu
14902                .completions
14903                .borrow()
14904                .iter()
14905                .map(|c| c.label.text.clone())
14906                .collect::<Vec<_>>();
14907            assert_eq!(
14908                completion_labels,
14909                &["registered_command", "unregistered_command",],
14910            );
14911        } else {
14912            panic!("expected completion menu to be open");
14913        }
14914    });
14915    editor
14916        .update_in(cx, |editor, window, cx| {
14917            editor.context_menu_next(&Default::default(), window, cx);
14918            editor
14919                .confirm_completion(&ConfirmCompletion::default(), window, cx)
14920                .unwrap()
14921        })
14922        .await
14923        .unwrap();
14924    cx.run_until_parked();
14925    assert_eq!(
14926        command_calls.load(atomic::Ordering::Acquire),
14927        1,
14928        "For completion with an unregistered command, Zed should not send a command execution request",
14929    );
14930}
14931
14932#[gpui::test]
14933async fn test_completion_reuse(cx: &mut TestAppContext) {
14934    init_test(cx, |_| {});
14935
14936    let mut cx = EditorLspTestContext::new_rust(
14937        lsp::ServerCapabilities {
14938            completion_provider: Some(lsp::CompletionOptions {
14939                trigger_characters: Some(vec![".".to_string()]),
14940                ..Default::default()
14941            }),
14942            ..Default::default()
14943        },
14944        cx,
14945    )
14946    .await;
14947
14948    let counter = Arc::new(AtomicUsize::new(0));
14949    cx.set_state("objˇ");
14950    cx.simulate_keystroke(".");
14951
14952    // Initial completion request returns complete results
14953    let is_incomplete = false;
14954    handle_completion_request(
14955        "obj.|<>",
14956        vec!["a", "ab", "abc"],
14957        is_incomplete,
14958        counter.clone(),
14959        &mut cx,
14960    )
14961    .await;
14962    cx.run_until_parked();
14963    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14964    cx.assert_editor_state("obj.ˇ");
14965    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14966
14967    // Type "a" - filters existing completions
14968    cx.simulate_keystroke("a");
14969    cx.run_until_parked();
14970    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14971    cx.assert_editor_state("obj.aˇ");
14972    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14973
14974    // Type "b" - filters existing completions
14975    cx.simulate_keystroke("b");
14976    cx.run_until_parked();
14977    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14978    cx.assert_editor_state("obj.abˇ");
14979    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14980
14981    // Type "c" - filters existing completions
14982    cx.simulate_keystroke("c");
14983    cx.run_until_parked();
14984    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14985    cx.assert_editor_state("obj.abcˇ");
14986    check_displayed_completions(vec!["abc"], &mut cx);
14987
14988    // Backspace to delete "c" - filters existing completions
14989    cx.update_editor(|editor, window, cx| {
14990        editor.backspace(&Backspace, window, cx);
14991    });
14992    cx.run_until_parked();
14993    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14994    cx.assert_editor_state("obj.abˇ");
14995    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14996
14997    // Moving cursor to the left dismisses menu.
14998    cx.update_editor(|editor, window, cx| {
14999        editor.move_left(&MoveLeft, window, cx);
15000    });
15001    cx.run_until_parked();
15002    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15003    cx.assert_editor_state("obj.aˇb");
15004    cx.update_editor(|editor, _, _| {
15005        assert_eq!(editor.context_menu_visible(), false);
15006    });
15007
15008    // Type "b" - new request
15009    cx.simulate_keystroke("b");
15010    let is_incomplete = false;
15011    handle_completion_request(
15012        "obj.<ab|>a",
15013        vec!["ab", "abc"],
15014        is_incomplete,
15015        counter.clone(),
15016        &mut cx,
15017    )
15018    .await;
15019    cx.run_until_parked();
15020    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15021    cx.assert_editor_state("obj.abˇb");
15022    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15023
15024    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15025    cx.update_editor(|editor, window, cx| {
15026        editor.backspace(&Backspace, window, cx);
15027    });
15028    let is_incomplete = false;
15029    handle_completion_request(
15030        "obj.<a|>b",
15031        vec!["a", "ab", "abc"],
15032        is_incomplete,
15033        counter.clone(),
15034        &mut cx,
15035    )
15036    .await;
15037    cx.run_until_parked();
15038    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15039    cx.assert_editor_state("obj.aˇb");
15040    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15041
15042    // Backspace to delete "a" - dismisses menu.
15043    cx.update_editor(|editor, window, cx| {
15044        editor.backspace(&Backspace, window, cx);
15045    });
15046    cx.run_until_parked();
15047    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15048    cx.assert_editor_state("obj.ˇb");
15049    cx.update_editor(|editor, _, _| {
15050        assert_eq!(editor.context_menu_visible(), false);
15051    });
15052}
15053
15054#[gpui::test]
15055async fn test_word_completion(cx: &mut TestAppContext) {
15056    let lsp_fetch_timeout_ms = 10;
15057    init_test(cx, |language_settings| {
15058        language_settings.defaults.completions = Some(CompletionSettingsContent {
15059            words_min_length: Some(0),
15060            lsp_fetch_timeout_ms: Some(10),
15061            lsp_insert_mode: Some(LspInsertMode::Insert),
15062            ..Default::default()
15063        });
15064    });
15065
15066    let mut cx = EditorLspTestContext::new_rust(
15067        lsp::ServerCapabilities {
15068            completion_provider: Some(lsp::CompletionOptions {
15069                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15070                ..lsp::CompletionOptions::default()
15071            }),
15072            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15073            ..lsp::ServerCapabilities::default()
15074        },
15075        cx,
15076    )
15077    .await;
15078
15079    let throttle_completions = Arc::new(AtomicBool::new(false));
15080
15081    let lsp_throttle_completions = throttle_completions.clone();
15082    let _completion_requests_handler =
15083        cx.lsp
15084            .server
15085            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15086                let lsp_throttle_completions = lsp_throttle_completions.clone();
15087                let cx = cx.clone();
15088                async move {
15089                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15090                        cx.background_executor()
15091                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15092                            .await;
15093                    }
15094                    Ok(Some(lsp::CompletionResponse::Array(vec![
15095                        lsp::CompletionItem {
15096                            label: "first".into(),
15097                            ..lsp::CompletionItem::default()
15098                        },
15099                        lsp::CompletionItem {
15100                            label: "last".into(),
15101                            ..lsp::CompletionItem::default()
15102                        },
15103                    ])))
15104                }
15105            });
15106
15107    cx.set_state(indoc! {"
15108        oneˇ
15109        two
15110        three
15111    "});
15112    cx.simulate_keystroke(".");
15113    cx.executor().run_until_parked();
15114    cx.condition(|editor, _| editor.context_menu_visible())
15115        .await;
15116    cx.update_editor(|editor, window, cx| {
15117        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15118        {
15119            assert_eq!(
15120                completion_menu_entries(menu),
15121                &["first", "last"],
15122                "When LSP server is fast to reply, no fallback word completions are used"
15123            );
15124        } else {
15125            panic!("expected completion menu to be open");
15126        }
15127        editor.cancel(&Cancel, window, cx);
15128    });
15129    cx.executor().run_until_parked();
15130    cx.condition(|editor, _| !editor.context_menu_visible())
15131        .await;
15132
15133    throttle_completions.store(true, atomic::Ordering::Release);
15134    cx.simulate_keystroke(".");
15135    cx.executor()
15136        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15137    cx.executor().run_until_parked();
15138    cx.condition(|editor, _| editor.context_menu_visible())
15139        .await;
15140    cx.update_editor(|editor, _, _| {
15141        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15142        {
15143            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15144                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15145        } else {
15146            panic!("expected completion menu to be open");
15147        }
15148    });
15149}
15150
15151#[gpui::test]
15152async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15153    init_test(cx, |language_settings| {
15154        language_settings.defaults.completions = Some(CompletionSettingsContent {
15155            words: Some(WordsCompletionMode::Enabled),
15156            words_min_length: Some(0),
15157            lsp_insert_mode: Some(LspInsertMode::Insert),
15158            ..Default::default()
15159        });
15160    });
15161
15162    let mut cx = EditorLspTestContext::new_rust(
15163        lsp::ServerCapabilities {
15164            completion_provider: Some(lsp::CompletionOptions {
15165                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15166                ..lsp::CompletionOptions::default()
15167            }),
15168            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15169            ..lsp::ServerCapabilities::default()
15170        },
15171        cx,
15172    )
15173    .await;
15174
15175    let _completion_requests_handler =
15176        cx.lsp
15177            .server
15178            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15179                Ok(Some(lsp::CompletionResponse::Array(vec![
15180                    lsp::CompletionItem {
15181                        label: "first".into(),
15182                        ..lsp::CompletionItem::default()
15183                    },
15184                    lsp::CompletionItem {
15185                        label: "last".into(),
15186                        ..lsp::CompletionItem::default()
15187                    },
15188                ])))
15189            });
15190
15191    cx.set_state(indoc! {"ˇ
15192        first
15193        last
15194        second
15195    "});
15196    cx.simulate_keystroke(".");
15197    cx.executor().run_until_parked();
15198    cx.condition(|editor, _| editor.context_menu_visible())
15199        .await;
15200    cx.update_editor(|editor, _, _| {
15201        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15202        {
15203            assert_eq!(
15204                completion_menu_entries(menu),
15205                &["first", "last", "second"],
15206                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15207            );
15208        } else {
15209            panic!("expected completion menu to be open");
15210        }
15211    });
15212}
15213
15214#[gpui::test]
15215async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15216    init_test(cx, |language_settings| {
15217        language_settings.defaults.completions = Some(CompletionSettingsContent {
15218            words: Some(WordsCompletionMode::Disabled),
15219            words_min_length: Some(0),
15220            lsp_insert_mode: Some(LspInsertMode::Insert),
15221            ..Default::default()
15222        });
15223    });
15224
15225    let mut cx = EditorLspTestContext::new_rust(
15226        lsp::ServerCapabilities {
15227            completion_provider: Some(lsp::CompletionOptions {
15228                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15229                ..lsp::CompletionOptions::default()
15230            }),
15231            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15232            ..lsp::ServerCapabilities::default()
15233        },
15234        cx,
15235    )
15236    .await;
15237
15238    let _completion_requests_handler =
15239        cx.lsp
15240            .server
15241            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15242                panic!("LSP completions should not be queried when dealing with word completions")
15243            });
15244
15245    cx.set_state(indoc! {"ˇ
15246        first
15247        last
15248        second
15249    "});
15250    cx.update_editor(|editor, window, cx| {
15251        editor.show_word_completions(&ShowWordCompletions, window, cx);
15252    });
15253    cx.executor().run_until_parked();
15254    cx.condition(|editor, _| editor.context_menu_visible())
15255        .await;
15256    cx.update_editor(|editor, _, _| {
15257        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15258        {
15259            assert_eq!(
15260                completion_menu_entries(menu),
15261                &["first", "last", "second"],
15262                "`ShowWordCompletions` action should show word completions"
15263            );
15264        } else {
15265            panic!("expected completion menu to be open");
15266        }
15267    });
15268
15269    cx.simulate_keystroke("l");
15270    cx.executor().run_until_parked();
15271    cx.condition(|editor, _| editor.context_menu_visible())
15272        .await;
15273    cx.update_editor(|editor, _, _| {
15274        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15275        {
15276            assert_eq!(
15277                completion_menu_entries(menu),
15278                &["last"],
15279                "After showing word completions, further editing should filter them and not query the LSP"
15280            );
15281        } else {
15282            panic!("expected completion menu to be open");
15283        }
15284    });
15285}
15286
15287#[gpui::test]
15288async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15289    init_test(cx, |language_settings| {
15290        language_settings.defaults.completions = Some(CompletionSettingsContent {
15291            words_min_length: Some(0),
15292            lsp: Some(false),
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
15300    cx.set_state(indoc! {"ˇ
15301        0_usize
15302        let
15303        33
15304        4.5f32
15305    "});
15306    cx.update_editor(|editor, window, cx| {
15307        editor.show_completions(&ShowCompletions, window, cx);
15308    });
15309    cx.executor().run_until_parked();
15310    cx.condition(|editor, _| editor.context_menu_visible())
15311        .await;
15312    cx.update_editor(|editor, window, cx| {
15313        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15314        {
15315            assert_eq!(
15316                completion_menu_entries(menu),
15317                &["let"],
15318                "With no digits in the completion query, no digits should be in the word completions"
15319            );
15320        } else {
15321            panic!("expected completion menu to be open");
15322        }
15323        editor.cancel(&Cancel, window, cx);
15324    });
15325
15326    cx.set_state(indoc! {"15327        0_usize
15328        let
15329        3
15330        33.35f32
15331    "});
15332    cx.update_editor(|editor, window, cx| {
15333        editor.show_completions(&ShowCompletions, window, cx);
15334    });
15335    cx.executor().run_until_parked();
15336    cx.condition(|editor, _| editor.context_menu_visible())
15337        .await;
15338    cx.update_editor(|editor, _, _| {
15339        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15340        {
15341            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15342                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15343        } else {
15344            panic!("expected completion menu to be open");
15345        }
15346    });
15347}
15348
15349#[gpui::test]
15350async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15351    init_test(cx, |language_settings| {
15352        language_settings.defaults.completions = Some(CompletionSettingsContent {
15353            words: Some(WordsCompletionMode::Enabled),
15354            words_min_length: Some(3),
15355            lsp_insert_mode: Some(LspInsertMode::Insert),
15356            ..Default::default()
15357        });
15358    });
15359
15360    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15361    cx.set_state(indoc! {"ˇ
15362        wow
15363        wowen
15364        wowser
15365    "});
15366    cx.simulate_keystroke("w");
15367    cx.executor().run_until_parked();
15368    cx.update_editor(|editor, _, _| {
15369        if editor.context_menu.borrow_mut().is_some() {
15370            panic!(
15371                "expected completion menu to be hidden, as words completion threshold is not met"
15372            );
15373        }
15374    });
15375
15376    cx.update_editor(|editor, window, cx| {
15377        editor.show_word_completions(&ShowWordCompletions, window, cx);
15378    });
15379    cx.executor().run_until_parked();
15380    cx.update_editor(|editor, window, cx| {
15381        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15382        {
15383            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");
15384        } else {
15385            panic!("expected completion menu to be open after the word completions are called with an action");
15386        }
15387
15388        editor.cancel(&Cancel, window, cx);
15389    });
15390    cx.update_editor(|editor, _, _| {
15391        if editor.context_menu.borrow_mut().is_some() {
15392            panic!("expected completion menu to be hidden after canceling");
15393        }
15394    });
15395
15396    cx.simulate_keystroke("o");
15397    cx.executor().run_until_parked();
15398    cx.update_editor(|editor, _, _| {
15399        if editor.context_menu.borrow_mut().is_some() {
15400            panic!(
15401                "expected completion menu to be hidden, as words completion threshold is not met still"
15402            );
15403        }
15404    });
15405
15406    cx.simulate_keystroke("w");
15407    cx.executor().run_until_parked();
15408    cx.update_editor(|editor, _, _| {
15409        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15410        {
15411            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15412        } else {
15413            panic!("expected completion menu to be open after the word completions threshold is met");
15414        }
15415    });
15416}
15417
15418#[gpui::test]
15419async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15420    init_test(cx, |language_settings| {
15421        language_settings.defaults.completions = Some(CompletionSettingsContent {
15422            words: Some(WordsCompletionMode::Enabled),
15423            words_min_length: Some(0),
15424            lsp_insert_mode: Some(LspInsertMode::Insert),
15425            ..Default::default()
15426        });
15427    });
15428
15429    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15430    cx.update_editor(|editor, _, _| {
15431        editor.disable_word_completions();
15432    });
15433    cx.set_state(indoc! {"ˇ
15434        wow
15435        wowen
15436        wowser
15437    "});
15438    cx.simulate_keystroke("w");
15439    cx.executor().run_until_parked();
15440    cx.update_editor(|editor, _, _| {
15441        if editor.context_menu.borrow_mut().is_some() {
15442            panic!(
15443                "expected completion menu to be hidden, as words completion are disabled for this editor"
15444            );
15445        }
15446    });
15447
15448    cx.update_editor(|editor, window, cx| {
15449        editor.show_word_completions(&ShowWordCompletions, window, cx);
15450    });
15451    cx.executor().run_until_parked();
15452    cx.update_editor(|editor, _, _| {
15453        if editor.context_menu.borrow_mut().is_some() {
15454            panic!(
15455                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15456            );
15457        }
15458    });
15459}
15460
15461#[gpui::test]
15462async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15463    init_test(cx, |language_settings| {
15464        language_settings.defaults.completions = Some(CompletionSettingsContent {
15465            words: Some(WordsCompletionMode::Disabled),
15466            words_min_length: Some(0),
15467            lsp_insert_mode: Some(LspInsertMode::Insert),
15468            ..Default::default()
15469        });
15470    });
15471
15472    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15473    cx.update_editor(|editor, _, _| {
15474        editor.set_completion_provider(None);
15475    });
15476    cx.set_state(indoc! {"ˇ
15477        wow
15478        wowen
15479        wowser
15480    "});
15481    cx.simulate_keystroke("w");
15482    cx.executor().run_until_parked();
15483    cx.update_editor(|editor, _, _| {
15484        if editor.context_menu.borrow_mut().is_some() {
15485            panic!("expected completion menu to be hidden, as disabled in settings");
15486        }
15487    });
15488}
15489
15490fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15491    let position = || lsp::Position {
15492        line: params.text_document_position.position.line,
15493        character: params.text_document_position.position.character,
15494    };
15495    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15496        range: lsp::Range {
15497            start: position(),
15498            end: position(),
15499        },
15500        new_text: text.to_string(),
15501    }))
15502}
15503
15504#[gpui::test]
15505async fn test_multiline_completion(cx: &mut TestAppContext) {
15506    init_test(cx, |_| {});
15507
15508    let fs = FakeFs::new(cx.executor());
15509    fs.insert_tree(
15510        path!("/a"),
15511        json!({
15512            "main.ts": "a",
15513        }),
15514    )
15515    .await;
15516
15517    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15518    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15519    let typescript_language = Arc::new(Language::new(
15520        LanguageConfig {
15521            name: "TypeScript".into(),
15522            matcher: LanguageMatcher {
15523                path_suffixes: vec!["ts".to_string()],
15524                ..LanguageMatcher::default()
15525            },
15526            line_comments: vec!["// ".into()],
15527            ..LanguageConfig::default()
15528        },
15529        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15530    ));
15531    language_registry.add(typescript_language.clone());
15532    let mut fake_servers = language_registry.register_fake_lsp(
15533        "TypeScript",
15534        FakeLspAdapter {
15535            capabilities: lsp::ServerCapabilities {
15536                completion_provider: Some(lsp::CompletionOptions {
15537                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15538                    ..lsp::CompletionOptions::default()
15539                }),
15540                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15541                ..lsp::ServerCapabilities::default()
15542            },
15543            // Emulate vtsls label generation
15544            label_for_completion: Some(Box::new(|item, _| {
15545                let text = if let Some(description) = item
15546                    .label_details
15547                    .as_ref()
15548                    .and_then(|label_details| label_details.description.as_ref())
15549                {
15550                    format!("{} {}", item.label, description)
15551                } else if let Some(detail) = &item.detail {
15552                    format!("{} {}", item.label, detail)
15553                } else {
15554                    item.label.clone()
15555                };
15556                Some(language::CodeLabel::plain(text, None))
15557            })),
15558            ..FakeLspAdapter::default()
15559        },
15560    );
15561    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15562    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15563    let worktree_id = workspace
15564        .update(cx, |workspace, _window, cx| {
15565            workspace.project().update(cx, |project, cx| {
15566                project.worktrees(cx).next().unwrap().read(cx).id()
15567            })
15568        })
15569        .unwrap();
15570    let _buffer = project
15571        .update(cx, |project, cx| {
15572            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15573        })
15574        .await
15575        .unwrap();
15576    let editor = workspace
15577        .update(cx, |workspace, window, cx| {
15578            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15579        })
15580        .unwrap()
15581        .await
15582        .unwrap()
15583        .downcast::<Editor>()
15584        .unwrap();
15585    let fake_server = fake_servers.next().await.unwrap();
15586
15587    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15588    let multiline_label_2 = "a\nb\nc\n";
15589    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15590    let multiline_description = "d\ne\nf\n";
15591    let multiline_detail_2 = "g\nh\ni\n";
15592
15593    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15594        move |params, _| async move {
15595            Ok(Some(lsp::CompletionResponse::Array(vec![
15596                lsp::CompletionItem {
15597                    label: multiline_label.to_string(),
15598                    text_edit: gen_text_edit(&params, "new_text_1"),
15599                    ..lsp::CompletionItem::default()
15600                },
15601                lsp::CompletionItem {
15602                    label: "single line label 1".to_string(),
15603                    detail: Some(multiline_detail.to_string()),
15604                    text_edit: gen_text_edit(&params, "new_text_2"),
15605                    ..lsp::CompletionItem::default()
15606                },
15607                lsp::CompletionItem {
15608                    label: "single line label 2".to_string(),
15609                    label_details: Some(lsp::CompletionItemLabelDetails {
15610                        description: Some(multiline_description.to_string()),
15611                        detail: None,
15612                    }),
15613                    text_edit: gen_text_edit(&params, "new_text_2"),
15614                    ..lsp::CompletionItem::default()
15615                },
15616                lsp::CompletionItem {
15617                    label: multiline_label_2.to_string(),
15618                    detail: Some(multiline_detail_2.to_string()),
15619                    text_edit: gen_text_edit(&params, "new_text_3"),
15620                    ..lsp::CompletionItem::default()
15621                },
15622                lsp::CompletionItem {
15623                    label: "Label with many     spaces and \t but without newlines".to_string(),
15624                    detail: Some(
15625                        "Details with many     spaces and \t but without newlines".to_string(),
15626                    ),
15627                    text_edit: gen_text_edit(&params, "new_text_4"),
15628                    ..lsp::CompletionItem::default()
15629                },
15630            ])))
15631        },
15632    );
15633
15634    editor.update_in(cx, |editor, window, cx| {
15635        cx.focus_self(window);
15636        editor.move_to_end(&MoveToEnd, window, cx);
15637        editor.handle_input(".", window, cx);
15638    });
15639    cx.run_until_parked();
15640    completion_handle.next().await.unwrap();
15641
15642    editor.update(cx, |editor, _| {
15643        assert!(editor.context_menu_visible());
15644        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15645        {
15646            let completion_labels = menu
15647                .completions
15648                .borrow()
15649                .iter()
15650                .map(|c| c.label.text.clone())
15651                .collect::<Vec<_>>();
15652            assert_eq!(
15653                completion_labels,
15654                &[
15655                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15656                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15657                    "single line label 2 d e f ",
15658                    "a b c g h i ",
15659                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15660                ],
15661                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15662            );
15663
15664            for completion in menu
15665                .completions
15666                .borrow()
15667                .iter() {
15668                    assert_eq!(
15669                        completion.label.filter_range,
15670                        0..completion.label.text.len(),
15671                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15672                    );
15673                }
15674        } else {
15675            panic!("expected completion menu to be open");
15676        }
15677    });
15678}
15679
15680#[gpui::test]
15681async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15682    init_test(cx, |_| {});
15683    let mut cx = EditorLspTestContext::new_rust(
15684        lsp::ServerCapabilities {
15685            completion_provider: Some(lsp::CompletionOptions {
15686                trigger_characters: Some(vec![".".to_string()]),
15687                ..Default::default()
15688            }),
15689            ..Default::default()
15690        },
15691        cx,
15692    )
15693    .await;
15694    cx.lsp
15695        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15696            Ok(Some(lsp::CompletionResponse::Array(vec![
15697                lsp::CompletionItem {
15698                    label: "first".into(),
15699                    ..Default::default()
15700                },
15701                lsp::CompletionItem {
15702                    label: "last".into(),
15703                    ..Default::default()
15704                },
15705            ])))
15706        });
15707    cx.set_state("variableˇ");
15708    cx.simulate_keystroke(".");
15709    cx.executor().run_until_parked();
15710
15711    cx.update_editor(|editor, _, _| {
15712        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15713        {
15714            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15715        } else {
15716            panic!("expected completion menu to be open");
15717        }
15718    });
15719
15720    cx.update_editor(|editor, window, cx| {
15721        editor.move_page_down(&MovePageDown::default(), window, cx);
15722        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15723        {
15724            assert!(
15725                menu.selected_item == 1,
15726                "expected PageDown to select the last item from the context menu"
15727            );
15728        } else {
15729            panic!("expected completion menu to stay open after PageDown");
15730        }
15731    });
15732
15733    cx.update_editor(|editor, window, cx| {
15734        editor.move_page_up(&MovePageUp::default(), window, cx);
15735        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15736        {
15737            assert!(
15738                menu.selected_item == 0,
15739                "expected PageUp to select the first item from the context menu"
15740            );
15741        } else {
15742            panic!("expected completion menu to stay open after PageUp");
15743        }
15744    });
15745}
15746
15747#[gpui::test]
15748async fn test_as_is_completions(cx: &mut TestAppContext) {
15749    init_test(cx, |_| {});
15750    let mut cx = EditorLspTestContext::new_rust(
15751        lsp::ServerCapabilities {
15752            completion_provider: Some(lsp::CompletionOptions {
15753                ..Default::default()
15754            }),
15755            ..Default::default()
15756        },
15757        cx,
15758    )
15759    .await;
15760    cx.lsp
15761        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15762            Ok(Some(lsp::CompletionResponse::Array(vec![
15763                lsp::CompletionItem {
15764                    label: "unsafe".into(),
15765                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15766                        range: lsp::Range {
15767                            start: lsp::Position {
15768                                line: 1,
15769                                character: 2,
15770                            },
15771                            end: lsp::Position {
15772                                line: 1,
15773                                character: 3,
15774                            },
15775                        },
15776                        new_text: "unsafe".to_string(),
15777                    })),
15778                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15779                    ..Default::default()
15780                },
15781            ])))
15782        });
15783    cx.set_state("fn a() {}\n");
15784    cx.executor().run_until_parked();
15785    cx.update_editor(|editor, window, cx| {
15786        editor.trigger_completion_on_input("n", true, window, cx)
15787    });
15788    cx.executor().run_until_parked();
15789
15790    cx.update_editor(|editor, window, cx| {
15791        editor.confirm_completion(&Default::default(), window, cx)
15792    });
15793    cx.executor().run_until_parked();
15794    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15795}
15796
15797#[gpui::test]
15798async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15799    init_test(cx, |_| {});
15800    let language =
15801        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15802    let mut cx = EditorLspTestContext::new(
15803        language,
15804        lsp::ServerCapabilities {
15805            completion_provider: Some(lsp::CompletionOptions {
15806                ..lsp::CompletionOptions::default()
15807            }),
15808            ..lsp::ServerCapabilities::default()
15809        },
15810        cx,
15811    )
15812    .await;
15813
15814    cx.set_state(
15815        "#ifndef BAR_H
15816#define BAR_H
15817
15818#include <stdbool.h>
15819
15820int fn_branch(bool do_branch1, bool do_branch2);
15821
15822#endif // BAR_H
15823ˇ",
15824    );
15825    cx.executor().run_until_parked();
15826    cx.update_editor(|editor, window, cx| {
15827        editor.handle_input("#", window, cx);
15828    });
15829    cx.executor().run_until_parked();
15830    cx.update_editor(|editor, window, cx| {
15831        editor.handle_input("i", window, cx);
15832    });
15833    cx.executor().run_until_parked();
15834    cx.update_editor(|editor, window, cx| {
15835        editor.handle_input("n", window, cx);
15836    });
15837    cx.executor().run_until_parked();
15838    cx.assert_editor_state(
15839        "#ifndef BAR_H
15840#define BAR_H
15841
15842#include <stdbool.h>
15843
15844int fn_branch(bool do_branch1, bool do_branch2);
15845
15846#endif // BAR_H
15847#inˇ",
15848    );
15849
15850    cx.lsp
15851        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15852            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15853                is_incomplete: false,
15854                item_defaults: None,
15855                items: vec![lsp::CompletionItem {
15856                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15857                    label_details: Some(lsp::CompletionItemLabelDetails {
15858                        detail: Some("header".to_string()),
15859                        description: None,
15860                    }),
15861                    label: " include".to_string(),
15862                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15863                        range: lsp::Range {
15864                            start: lsp::Position {
15865                                line: 8,
15866                                character: 1,
15867                            },
15868                            end: lsp::Position {
15869                                line: 8,
15870                                character: 1,
15871                            },
15872                        },
15873                        new_text: "include \"$0\"".to_string(),
15874                    })),
15875                    sort_text: Some("40b67681include".to_string()),
15876                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15877                    filter_text: Some("include".to_string()),
15878                    insert_text: Some("include \"$0\"".to_string()),
15879                    ..lsp::CompletionItem::default()
15880                }],
15881            })))
15882        });
15883    cx.update_editor(|editor, window, cx| {
15884        editor.show_completions(&ShowCompletions, window, cx);
15885    });
15886    cx.executor().run_until_parked();
15887    cx.update_editor(|editor, window, cx| {
15888        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15889    });
15890    cx.executor().run_until_parked();
15891    cx.assert_editor_state(
15892        "#ifndef BAR_H
15893#define BAR_H
15894
15895#include <stdbool.h>
15896
15897int fn_branch(bool do_branch1, bool do_branch2);
15898
15899#endif // BAR_H
15900#include \"ˇ\"",
15901    );
15902
15903    cx.lsp
15904        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15905            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15906                is_incomplete: true,
15907                item_defaults: None,
15908                items: vec![lsp::CompletionItem {
15909                    kind: Some(lsp::CompletionItemKind::FILE),
15910                    label: "AGL/".to_string(),
15911                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15912                        range: lsp::Range {
15913                            start: lsp::Position {
15914                                line: 8,
15915                                character: 10,
15916                            },
15917                            end: lsp::Position {
15918                                line: 8,
15919                                character: 11,
15920                            },
15921                        },
15922                        new_text: "AGL/".to_string(),
15923                    })),
15924                    sort_text: Some("40b67681AGL/".to_string()),
15925                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15926                    filter_text: Some("AGL/".to_string()),
15927                    insert_text: Some("AGL/".to_string()),
15928                    ..lsp::CompletionItem::default()
15929                }],
15930            })))
15931        });
15932    cx.update_editor(|editor, window, cx| {
15933        editor.show_completions(&ShowCompletions, window, cx);
15934    });
15935    cx.executor().run_until_parked();
15936    cx.update_editor(|editor, window, cx| {
15937        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15938    });
15939    cx.executor().run_until_parked();
15940    cx.assert_editor_state(
15941        r##"#ifndef BAR_H
15942#define BAR_H
15943
15944#include <stdbool.h>
15945
15946int fn_branch(bool do_branch1, bool do_branch2);
15947
15948#endif // BAR_H
15949#include "AGL/ˇ"##,
15950    );
15951
15952    cx.update_editor(|editor, window, cx| {
15953        editor.handle_input("\"", window, cx);
15954    });
15955    cx.executor().run_until_parked();
15956    cx.assert_editor_state(
15957        r##"#ifndef BAR_H
15958#define BAR_H
15959
15960#include <stdbool.h>
15961
15962int fn_branch(bool do_branch1, bool do_branch2);
15963
15964#endif // BAR_H
15965#include "AGL/"ˇ"##,
15966    );
15967}
15968
15969#[gpui::test]
15970async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15971    init_test(cx, |_| {});
15972
15973    let mut cx = EditorLspTestContext::new_rust(
15974        lsp::ServerCapabilities {
15975            completion_provider: Some(lsp::CompletionOptions {
15976                trigger_characters: Some(vec![".".to_string()]),
15977                resolve_provider: Some(true),
15978                ..Default::default()
15979            }),
15980            ..Default::default()
15981        },
15982        cx,
15983    )
15984    .await;
15985
15986    cx.set_state("fn main() { let a = 2ˇ; }");
15987    cx.simulate_keystroke(".");
15988    let completion_item = lsp::CompletionItem {
15989        label: "Some".into(),
15990        kind: Some(lsp::CompletionItemKind::SNIPPET),
15991        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15992        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15993            kind: lsp::MarkupKind::Markdown,
15994            value: "```rust\nSome(2)\n```".to_string(),
15995        })),
15996        deprecated: Some(false),
15997        sort_text: Some("Some".to_string()),
15998        filter_text: Some("Some".to_string()),
15999        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16000        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16001            range: lsp::Range {
16002                start: lsp::Position {
16003                    line: 0,
16004                    character: 22,
16005                },
16006                end: lsp::Position {
16007                    line: 0,
16008                    character: 22,
16009                },
16010            },
16011            new_text: "Some(2)".to_string(),
16012        })),
16013        additional_text_edits: Some(vec![lsp::TextEdit {
16014            range: lsp::Range {
16015                start: lsp::Position {
16016                    line: 0,
16017                    character: 20,
16018                },
16019                end: lsp::Position {
16020                    line: 0,
16021                    character: 22,
16022                },
16023            },
16024            new_text: "".to_string(),
16025        }]),
16026        ..Default::default()
16027    };
16028
16029    let closure_completion_item = completion_item.clone();
16030    let counter = Arc::new(AtomicUsize::new(0));
16031    let counter_clone = counter.clone();
16032    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16033        let task_completion_item = closure_completion_item.clone();
16034        counter_clone.fetch_add(1, atomic::Ordering::Release);
16035        async move {
16036            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16037                is_incomplete: true,
16038                item_defaults: None,
16039                items: vec![task_completion_item],
16040            })))
16041        }
16042    });
16043
16044    cx.condition(|editor, _| editor.context_menu_visible())
16045        .await;
16046    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16047    assert!(request.next().await.is_some());
16048    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16049
16050    cx.simulate_keystrokes("S o m");
16051    cx.condition(|editor, _| editor.context_menu_visible())
16052        .await;
16053    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16054    assert!(request.next().await.is_some());
16055    assert!(request.next().await.is_some());
16056    assert!(request.next().await.is_some());
16057    request.close();
16058    assert!(request.next().await.is_none());
16059    assert_eq!(
16060        counter.load(atomic::Ordering::Acquire),
16061        4,
16062        "With the completions menu open, only one LSP request should happen per input"
16063    );
16064}
16065
16066#[gpui::test]
16067async fn test_toggle_comment(cx: &mut TestAppContext) {
16068    init_test(cx, |_| {});
16069    let mut cx = EditorTestContext::new(cx).await;
16070    let language = Arc::new(Language::new(
16071        LanguageConfig {
16072            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16073            ..Default::default()
16074        },
16075        Some(tree_sitter_rust::LANGUAGE.into()),
16076    ));
16077    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16078
16079    // If multiple selections intersect a line, the line is only toggled once.
16080    cx.set_state(indoc! {"
16081        fn a() {
16082            «//b();
16083            ˇ»// «c();
16084            //ˇ»  d();
16085        }
16086    "});
16087
16088    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16089
16090    cx.assert_editor_state(indoc! {"
16091        fn a() {
16092            «b();
16093            c();
16094            ˇ» d();
16095        }
16096    "});
16097
16098    // The comment prefix is inserted at the same column for every line in a
16099    // selection.
16100    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16101
16102    cx.assert_editor_state(indoc! {"
16103        fn a() {
16104            // «b();
16105            // c();
16106            ˇ»//  d();
16107        }
16108    "});
16109
16110    // If a selection ends at the beginning of a line, that line is not toggled.
16111    cx.set_selections_state(indoc! {"
16112        fn a() {
16113            // b();
16114            «// c();
16115        ˇ»    //  d();
16116        }
16117    "});
16118
16119    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16120
16121    cx.assert_editor_state(indoc! {"
16122        fn a() {
16123            // b();
16124            «c();
16125        ˇ»    //  d();
16126        }
16127    "});
16128
16129    // If a selection span a single line and is empty, the line is toggled.
16130    cx.set_state(indoc! {"
16131        fn a() {
16132            a();
16133            b();
16134        ˇ
16135        }
16136    "});
16137
16138    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16139
16140    cx.assert_editor_state(indoc! {"
16141        fn a() {
16142            a();
16143            b();
16144        //•ˇ
16145        }
16146    "});
16147
16148    // If a selection span multiple lines, empty lines are not toggled.
16149    cx.set_state(indoc! {"
16150        fn a() {
16151            «a();
16152
16153            c();ˇ»
16154        }
16155    "});
16156
16157    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16158
16159    cx.assert_editor_state(indoc! {"
16160        fn a() {
16161            // «a();
16162
16163            // c();ˇ»
16164        }
16165    "});
16166
16167    // If a selection includes multiple comment prefixes, all lines are uncommented.
16168    cx.set_state(indoc! {"
16169        fn a() {
16170            «// a();
16171            /// b();
16172            //! c();ˇ»
16173        }
16174    "});
16175
16176    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16177
16178    cx.assert_editor_state(indoc! {"
16179        fn a() {
16180            «a();
16181            b();
16182            c();ˇ»
16183        }
16184    "});
16185}
16186
16187#[gpui::test]
16188async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16189    init_test(cx, |_| {});
16190    let mut cx = EditorTestContext::new(cx).await;
16191    let language = Arc::new(Language::new(
16192        LanguageConfig {
16193            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16194            ..Default::default()
16195        },
16196        Some(tree_sitter_rust::LANGUAGE.into()),
16197    ));
16198    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16199
16200    let toggle_comments = &ToggleComments {
16201        advance_downwards: false,
16202        ignore_indent: true,
16203    };
16204
16205    // If multiple selections intersect a line, the line is only toggled once.
16206    cx.set_state(indoc! {"
16207        fn a() {
16208        //    «b();
16209        //    c();
16210        //    ˇ» d();
16211        }
16212    "});
16213
16214    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16215
16216    cx.assert_editor_state(indoc! {"
16217        fn a() {
16218            «b();
16219            c();
16220            ˇ» d();
16221        }
16222    "});
16223
16224    // The comment prefix is inserted at the beginning of each line
16225    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16226
16227    cx.assert_editor_state(indoc! {"
16228        fn a() {
16229        //    «b();
16230        //    c();
16231        //    ˇ» d();
16232        }
16233    "});
16234
16235    // If a selection ends at the beginning of a line, that line is not toggled.
16236    cx.set_selections_state(indoc! {"
16237        fn a() {
16238        //    b();
16239        //    «c();
16240        ˇ»//     d();
16241        }
16242    "});
16243
16244    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16245
16246    cx.assert_editor_state(indoc! {"
16247        fn a() {
16248        //    b();
16249            «c();
16250        ˇ»//     d();
16251        }
16252    "});
16253
16254    // If a selection span a single line and is empty, the line is toggled.
16255    cx.set_state(indoc! {"
16256        fn a() {
16257            a();
16258            b();
16259        ˇ
16260        }
16261    "});
16262
16263    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16264
16265    cx.assert_editor_state(indoc! {"
16266        fn a() {
16267            a();
16268            b();
16269        //ˇ
16270        }
16271    "});
16272
16273    // If a selection span multiple lines, empty lines are not toggled.
16274    cx.set_state(indoc! {"
16275        fn a() {
16276            «a();
16277
16278            c();ˇ»
16279        }
16280    "});
16281
16282    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16283
16284    cx.assert_editor_state(indoc! {"
16285        fn a() {
16286        //    «a();
16287
16288        //    c();ˇ»
16289        }
16290    "});
16291
16292    // If a selection includes multiple comment prefixes, all lines are uncommented.
16293    cx.set_state(indoc! {"
16294        fn a() {
16295        //    «a();
16296        ///    b();
16297        //!    c();ˇ»
16298        }
16299    "});
16300
16301    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16302
16303    cx.assert_editor_state(indoc! {"
16304        fn a() {
16305            «a();
16306            b();
16307            c();ˇ»
16308        }
16309    "});
16310}
16311
16312#[gpui::test]
16313async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16314    init_test(cx, |_| {});
16315
16316    let language = Arc::new(Language::new(
16317        LanguageConfig {
16318            line_comments: vec!["// ".into()],
16319            ..Default::default()
16320        },
16321        Some(tree_sitter_rust::LANGUAGE.into()),
16322    ));
16323
16324    let mut cx = EditorTestContext::new(cx).await;
16325
16326    cx.language_registry().add(language.clone());
16327    cx.update_buffer(|buffer, cx| {
16328        buffer.set_language(Some(language), cx);
16329    });
16330
16331    let toggle_comments = &ToggleComments {
16332        advance_downwards: true,
16333        ignore_indent: false,
16334    };
16335
16336    // Single cursor on one line -> advance
16337    // Cursor moves horizontally 3 characters as well on non-blank line
16338    cx.set_state(indoc!(
16339        "fn a() {
16340             ˇdog();
16341             cat();
16342        }"
16343    ));
16344    cx.update_editor(|editor, window, cx| {
16345        editor.toggle_comments(toggle_comments, window, cx);
16346    });
16347    cx.assert_editor_state(indoc!(
16348        "fn a() {
16349             // dog();
16350             catˇ();
16351        }"
16352    ));
16353
16354    // Single selection on one line -> don't advance
16355    cx.set_state(indoc!(
16356        "fn a() {
16357             «dog()ˇ»;
16358             cat();
16359        }"
16360    ));
16361    cx.update_editor(|editor, window, cx| {
16362        editor.toggle_comments(toggle_comments, window, cx);
16363    });
16364    cx.assert_editor_state(indoc!(
16365        "fn a() {
16366             // «dog()ˇ»;
16367             cat();
16368        }"
16369    ));
16370
16371    // Multiple cursors on one line -> advance
16372    cx.set_state(indoc!(
16373        "fn a() {
16374             ˇdˇog();
16375             cat();
16376        }"
16377    ));
16378    cx.update_editor(|editor, window, cx| {
16379        editor.toggle_comments(toggle_comments, window, cx);
16380    });
16381    cx.assert_editor_state(indoc!(
16382        "fn a() {
16383             // dog();
16384             catˇ(ˇ);
16385        }"
16386    ));
16387
16388    // Multiple cursors on one line, with selection -> don't advance
16389    cx.set_state(indoc!(
16390        "fn a() {
16391             ˇdˇog«()ˇ»;
16392             cat();
16393        }"
16394    ));
16395    cx.update_editor(|editor, window, cx| {
16396        editor.toggle_comments(toggle_comments, window, cx);
16397    });
16398    cx.assert_editor_state(indoc!(
16399        "fn a() {
16400             // ˇdˇog«()ˇ»;
16401             cat();
16402        }"
16403    ));
16404
16405    // Single cursor on one line -> advance
16406    // Cursor moves to column 0 on blank line
16407    cx.set_state(indoc!(
16408        "fn a() {
16409             ˇdog();
16410
16411             cat();
16412        }"
16413    ));
16414    cx.update_editor(|editor, window, cx| {
16415        editor.toggle_comments(toggle_comments, window, cx);
16416    });
16417    cx.assert_editor_state(indoc!(
16418        "fn a() {
16419             // dog();
16420        ˇ
16421             cat();
16422        }"
16423    ));
16424
16425    // Single cursor on one line -> advance
16426    // Cursor starts and ends at column 0
16427    cx.set_state(indoc!(
16428        "fn a() {
16429         ˇ    dog();
16430             cat();
16431        }"
16432    ));
16433    cx.update_editor(|editor, window, cx| {
16434        editor.toggle_comments(toggle_comments, window, cx);
16435    });
16436    cx.assert_editor_state(indoc!(
16437        "fn a() {
16438             // dog();
16439         ˇ    cat();
16440        }"
16441    ));
16442}
16443
16444#[gpui::test]
16445async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16446    init_test(cx, |_| {});
16447
16448    let mut cx = EditorTestContext::new(cx).await;
16449
16450    let html_language = Arc::new(
16451        Language::new(
16452            LanguageConfig {
16453                name: "HTML".into(),
16454                block_comment: Some(BlockCommentConfig {
16455                    start: "<!-- ".into(),
16456                    prefix: "".into(),
16457                    end: " -->".into(),
16458                    tab_size: 0,
16459                }),
16460                ..Default::default()
16461            },
16462            Some(tree_sitter_html::LANGUAGE.into()),
16463        )
16464        .with_injection_query(
16465            r#"
16466            (script_element
16467                (raw_text) @injection.content
16468                (#set! injection.language "javascript"))
16469            "#,
16470        )
16471        .unwrap(),
16472    );
16473
16474    let javascript_language = Arc::new(Language::new(
16475        LanguageConfig {
16476            name: "JavaScript".into(),
16477            line_comments: vec!["// ".into()],
16478            ..Default::default()
16479        },
16480        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16481    ));
16482
16483    cx.language_registry().add(html_language.clone());
16484    cx.language_registry().add(javascript_language);
16485    cx.update_buffer(|buffer, cx| {
16486        buffer.set_language(Some(html_language), cx);
16487    });
16488
16489    // Toggle comments for empty selections
16490    cx.set_state(
16491        &r#"
16492            <p>A</p>ˇ
16493            <p>B</p>ˇ
16494            <p>C</p>ˇ
16495        "#
16496        .unindent(),
16497    );
16498    cx.update_editor(|editor, window, cx| {
16499        editor.toggle_comments(&ToggleComments::default(), window, cx)
16500    });
16501    cx.assert_editor_state(
16502        &r#"
16503            <!-- <p>A</p>ˇ -->
16504            <!-- <p>B</p>ˇ -->
16505            <!-- <p>C</p>ˇ -->
16506        "#
16507        .unindent(),
16508    );
16509    cx.update_editor(|editor, window, cx| {
16510        editor.toggle_comments(&ToggleComments::default(), window, cx)
16511    });
16512    cx.assert_editor_state(
16513        &r#"
16514            <p>A</p>ˇ
16515            <p>B</p>ˇ
16516            <p>C</p>ˇ
16517        "#
16518        .unindent(),
16519    );
16520
16521    // Toggle comments for mixture of empty and non-empty selections, where
16522    // multiple selections occupy a given line.
16523    cx.set_state(
16524        &r#"
16525            <p>A«</p>
16526            <p>ˇ»B</p>ˇ
16527            <p>C«</p>
16528            <p>ˇ»D</p>ˇ
16529        "#
16530        .unindent(),
16531    );
16532
16533    cx.update_editor(|editor, window, cx| {
16534        editor.toggle_comments(&ToggleComments::default(), window, cx)
16535    });
16536    cx.assert_editor_state(
16537        &r#"
16538            <!-- <p>A«</p>
16539            <p>ˇ»B</p>ˇ -->
16540            <!-- <p>C«</p>
16541            <p>ˇ»D</p>ˇ -->
16542        "#
16543        .unindent(),
16544    );
16545    cx.update_editor(|editor, window, cx| {
16546        editor.toggle_comments(&ToggleComments::default(), window, cx)
16547    });
16548    cx.assert_editor_state(
16549        &r#"
16550            <p>A«</p>
16551            <p>ˇ»B</p>ˇ
16552            <p>C«</p>
16553            <p>ˇ»D</p>ˇ
16554        "#
16555        .unindent(),
16556    );
16557
16558    // Toggle comments when different languages are active for different
16559    // selections.
16560    cx.set_state(
16561        &r#"
16562            ˇ<script>
16563                ˇvar x = new Y();
16564            ˇ</script>
16565        "#
16566        .unindent(),
16567    );
16568    cx.executor().run_until_parked();
16569    cx.update_editor(|editor, window, cx| {
16570        editor.toggle_comments(&ToggleComments::default(), window, cx)
16571    });
16572    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16573    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16574    cx.assert_editor_state(
16575        &r#"
16576            <!-- ˇ<script> -->
16577                // ˇvar x = new Y();
16578            <!-- ˇ</script> -->
16579        "#
16580        .unindent(),
16581    );
16582}
16583
16584#[gpui::test]
16585fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16586    init_test(cx, |_| {});
16587
16588    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16589    let multibuffer = cx.new(|cx| {
16590        let mut multibuffer = MultiBuffer::new(ReadWrite);
16591        multibuffer.push_excerpts(
16592            buffer.clone(),
16593            [
16594                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16595                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16596            ],
16597            cx,
16598        );
16599        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16600        multibuffer
16601    });
16602
16603    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16604    editor.update_in(cx, |editor, window, cx| {
16605        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16606        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16607            s.select_ranges([
16608                Point::new(0, 0)..Point::new(0, 0),
16609                Point::new(1, 0)..Point::new(1, 0),
16610            ])
16611        });
16612
16613        editor.handle_input("X", window, cx);
16614        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16615        assert_eq!(
16616            editor.selections.ranges(&editor.display_snapshot(cx)),
16617            [
16618                Point::new(0, 1)..Point::new(0, 1),
16619                Point::new(1, 1)..Point::new(1, 1),
16620            ]
16621        );
16622
16623        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16624        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16625            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16626        });
16627        editor.backspace(&Default::default(), window, cx);
16628        assert_eq!(editor.text(cx), "Xa\nbbb");
16629        assert_eq!(
16630            editor.selections.ranges(&editor.display_snapshot(cx)),
16631            [Point::new(1, 0)..Point::new(1, 0)]
16632        );
16633
16634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16635            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16636        });
16637        editor.backspace(&Default::default(), window, cx);
16638        assert_eq!(editor.text(cx), "X\nbb");
16639        assert_eq!(
16640            editor.selections.ranges(&editor.display_snapshot(cx)),
16641            [Point::new(0, 1)..Point::new(0, 1)]
16642        );
16643    });
16644}
16645
16646#[gpui::test]
16647fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16648    init_test(cx, |_| {});
16649
16650    let markers = vec![('[', ']').into(), ('(', ')').into()];
16651    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16652        indoc! {"
16653            [aaaa
16654            (bbbb]
16655            cccc)",
16656        },
16657        markers.clone(),
16658    );
16659    let excerpt_ranges = markers.into_iter().map(|marker| {
16660        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16661        ExcerptRange::new(context)
16662    });
16663    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16664    let multibuffer = cx.new(|cx| {
16665        let mut multibuffer = MultiBuffer::new(ReadWrite);
16666        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16667        multibuffer
16668    });
16669
16670    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16671    editor.update_in(cx, |editor, window, cx| {
16672        let (expected_text, selection_ranges) = marked_text_ranges(
16673            indoc! {"
16674                aaaa
16675                bˇbbb
16676                bˇbbˇb
16677                cccc"
16678            },
16679            true,
16680        );
16681        assert_eq!(editor.text(cx), expected_text);
16682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16683            s.select_ranges(
16684                selection_ranges
16685                    .iter()
16686                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16687            )
16688        });
16689
16690        editor.handle_input("X", window, cx);
16691
16692        let (expected_text, expected_selections) = marked_text_ranges(
16693            indoc! {"
16694                aaaa
16695                bXˇbbXb
16696                bXˇbbXˇb
16697                cccc"
16698            },
16699            false,
16700        );
16701        assert_eq!(editor.text(cx), expected_text);
16702        assert_eq!(
16703            editor.selections.ranges(&editor.display_snapshot(cx)),
16704            expected_selections
16705                .iter()
16706                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16707                .collect::<Vec<_>>()
16708        );
16709
16710        editor.newline(&Newline, window, cx);
16711        let (expected_text, expected_selections) = marked_text_ranges(
16712            indoc! {"
16713                aaaa
16714                bX
16715                ˇbbX
16716                b
16717                bX
16718                ˇbbX
16719                ˇb
16720                cccc"
16721            },
16722            false,
16723        );
16724        assert_eq!(editor.text(cx), expected_text);
16725        assert_eq!(
16726            editor.selections.ranges(&editor.display_snapshot(cx)),
16727            expected_selections
16728                .iter()
16729                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16730                .collect::<Vec<_>>()
16731        );
16732    });
16733}
16734
16735#[gpui::test]
16736fn test_refresh_selections(cx: &mut TestAppContext) {
16737    init_test(cx, |_| {});
16738
16739    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16740    let mut excerpt1_id = None;
16741    let multibuffer = cx.new(|cx| {
16742        let mut multibuffer = MultiBuffer::new(ReadWrite);
16743        excerpt1_id = multibuffer
16744            .push_excerpts(
16745                buffer.clone(),
16746                [
16747                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16748                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16749                ],
16750                cx,
16751            )
16752            .into_iter()
16753            .next();
16754        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16755        multibuffer
16756    });
16757
16758    let editor = cx.add_window(|window, cx| {
16759        let mut editor = build_editor(multibuffer.clone(), window, cx);
16760        let snapshot = editor.snapshot(window, cx);
16761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16762            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16763        });
16764        editor.begin_selection(
16765            Point::new(2, 1).to_display_point(&snapshot),
16766            true,
16767            1,
16768            window,
16769            cx,
16770        );
16771        assert_eq!(
16772            editor.selections.ranges(&editor.display_snapshot(cx)),
16773            [
16774                Point::new(1, 3)..Point::new(1, 3),
16775                Point::new(2, 1)..Point::new(2, 1),
16776            ]
16777        );
16778        editor
16779    });
16780
16781    // Refreshing selections is a no-op when excerpts haven't changed.
16782    _ = editor.update(cx, |editor, window, cx| {
16783        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16784        assert_eq!(
16785            editor.selections.ranges(&editor.display_snapshot(cx)),
16786            [
16787                Point::new(1, 3)..Point::new(1, 3),
16788                Point::new(2, 1)..Point::new(2, 1),
16789            ]
16790        );
16791    });
16792
16793    multibuffer.update(cx, |multibuffer, cx| {
16794        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16795    });
16796    _ = editor.update(cx, |editor, window, cx| {
16797        // Removing an excerpt causes the first selection to become degenerate.
16798        assert_eq!(
16799            editor.selections.ranges(&editor.display_snapshot(cx)),
16800            [
16801                Point::new(0, 0)..Point::new(0, 0),
16802                Point::new(0, 1)..Point::new(0, 1)
16803            ]
16804        );
16805
16806        // Refreshing selections will relocate the first selection to the original buffer
16807        // location.
16808        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16809        assert_eq!(
16810            editor.selections.ranges(&editor.display_snapshot(cx)),
16811            [
16812                Point::new(0, 1)..Point::new(0, 1),
16813                Point::new(0, 3)..Point::new(0, 3)
16814            ]
16815        );
16816        assert!(editor.selections.pending_anchor().is_some());
16817    });
16818}
16819
16820#[gpui::test]
16821fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16822    init_test(cx, |_| {});
16823
16824    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16825    let mut excerpt1_id = None;
16826    let multibuffer = cx.new(|cx| {
16827        let mut multibuffer = MultiBuffer::new(ReadWrite);
16828        excerpt1_id = multibuffer
16829            .push_excerpts(
16830                buffer.clone(),
16831                [
16832                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16833                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16834                ],
16835                cx,
16836            )
16837            .into_iter()
16838            .next();
16839        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16840        multibuffer
16841    });
16842
16843    let editor = cx.add_window(|window, cx| {
16844        let mut editor = build_editor(multibuffer.clone(), window, cx);
16845        let snapshot = editor.snapshot(window, cx);
16846        editor.begin_selection(
16847            Point::new(1, 3).to_display_point(&snapshot),
16848            false,
16849            1,
16850            window,
16851            cx,
16852        );
16853        assert_eq!(
16854            editor.selections.ranges(&editor.display_snapshot(cx)),
16855            [Point::new(1, 3)..Point::new(1, 3)]
16856        );
16857        editor
16858    });
16859
16860    multibuffer.update(cx, |multibuffer, cx| {
16861        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16862    });
16863    _ = editor.update(cx, |editor, window, cx| {
16864        assert_eq!(
16865            editor.selections.ranges(&editor.display_snapshot(cx)),
16866            [Point::new(0, 0)..Point::new(0, 0)]
16867        );
16868
16869        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16871        assert_eq!(
16872            editor.selections.ranges(&editor.display_snapshot(cx)),
16873            [Point::new(0, 3)..Point::new(0, 3)]
16874        );
16875        assert!(editor.selections.pending_anchor().is_some());
16876    });
16877}
16878
16879#[gpui::test]
16880async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16881    init_test(cx, |_| {});
16882
16883    let language = Arc::new(
16884        Language::new(
16885            LanguageConfig {
16886                brackets: BracketPairConfig {
16887                    pairs: vec![
16888                        BracketPair {
16889                            start: "{".to_string(),
16890                            end: "}".to_string(),
16891                            close: true,
16892                            surround: true,
16893                            newline: true,
16894                        },
16895                        BracketPair {
16896                            start: "/* ".to_string(),
16897                            end: " */".to_string(),
16898                            close: true,
16899                            surround: true,
16900                            newline: true,
16901                        },
16902                    ],
16903                    ..Default::default()
16904                },
16905                ..Default::default()
16906            },
16907            Some(tree_sitter_rust::LANGUAGE.into()),
16908        )
16909        .with_indents_query("")
16910        .unwrap(),
16911    );
16912
16913    let text = concat!(
16914        "{   }\n",     //
16915        "  x\n",       //
16916        "  /*   */\n", //
16917        "x\n",         //
16918        "{{} }\n",     //
16919    );
16920
16921    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16922    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16923    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16924    editor
16925        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16926        .await;
16927
16928    editor.update_in(cx, |editor, window, cx| {
16929        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16930            s.select_display_ranges([
16931                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16932                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16933                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16934            ])
16935        });
16936        editor.newline(&Newline, window, cx);
16937
16938        assert_eq!(
16939            editor.buffer().read(cx).read(cx).text(),
16940            concat!(
16941                "{ \n",    // Suppress rustfmt
16942                "\n",      //
16943                "}\n",     //
16944                "  x\n",   //
16945                "  /* \n", //
16946                "  \n",    //
16947                "  */\n",  //
16948                "x\n",     //
16949                "{{} \n",  //
16950                "}\n",     //
16951            )
16952        );
16953    });
16954}
16955
16956#[gpui::test]
16957fn test_highlighted_ranges(cx: &mut TestAppContext) {
16958    init_test(cx, |_| {});
16959
16960    let editor = cx.add_window(|window, cx| {
16961        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16962        build_editor(buffer, window, cx)
16963    });
16964
16965    _ = editor.update(cx, |editor, window, cx| {
16966        struct Type1;
16967        struct Type2;
16968
16969        let buffer = editor.buffer.read(cx).snapshot(cx);
16970
16971        let anchor_range =
16972            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16973
16974        editor.highlight_background::<Type1>(
16975            &[
16976                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16977                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16978                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16979                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16980            ],
16981            |_| Hsla::red(),
16982            cx,
16983        );
16984        editor.highlight_background::<Type2>(
16985            &[
16986                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16987                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16988                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16989                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16990            ],
16991            |_| Hsla::green(),
16992            cx,
16993        );
16994
16995        let snapshot = editor.snapshot(window, cx);
16996        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16997            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16998            &snapshot,
16999            cx.theme(),
17000        );
17001        assert_eq!(
17002            highlighted_ranges,
17003            &[
17004                (
17005                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17006                    Hsla::green(),
17007                ),
17008                (
17009                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17010                    Hsla::red(),
17011                ),
17012                (
17013                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17014                    Hsla::green(),
17015                ),
17016                (
17017                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17018                    Hsla::red(),
17019                ),
17020            ]
17021        );
17022        assert_eq!(
17023            editor.sorted_background_highlights_in_range(
17024                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17025                &snapshot,
17026                cx.theme(),
17027            ),
17028            &[(
17029                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17030                Hsla::red(),
17031            )]
17032        );
17033    });
17034}
17035
17036#[gpui::test]
17037async fn test_following(cx: &mut TestAppContext) {
17038    init_test(cx, |_| {});
17039
17040    let fs = FakeFs::new(cx.executor());
17041    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17042
17043    let buffer = project.update(cx, |project, cx| {
17044        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17045        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17046    });
17047    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17048    let follower = cx.update(|cx| {
17049        cx.open_window(
17050            WindowOptions {
17051                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17052                    gpui::Point::new(px(0.), px(0.)),
17053                    gpui::Point::new(px(10.), px(80.)),
17054                ))),
17055                ..Default::default()
17056            },
17057            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17058        )
17059        .unwrap()
17060    });
17061
17062    let is_still_following = Rc::new(RefCell::new(true));
17063    let follower_edit_event_count = Rc::new(RefCell::new(0));
17064    let pending_update = Rc::new(RefCell::new(None));
17065    let leader_entity = leader.root(cx).unwrap();
17066    let follower_entity = follower.root(cx).unwrap();
17067    _ = follower.update(cx, {
17068        let update = pending_update.clone();
17069        let is_still_following = is_still_following.clone();
17070        let follower_edit_event_count = follower_edit_event_count.clone();
17071        |_, window, cx| {
17072            cx.subscribe_in(
17073                &leader_entity,
17074                window,
17075                move |_, leader, event, window, cx| {
17076                    leader.read(cx).add_event_to_update_proto(
17077                        event,
17078                        &mut update.borrow_mut(),
17079                        window,
17080                        cx,
17081                    );
17082                },
17083            )
17084            .detach();
17085
17086            cx.subscribe_in(
17087                &follower_entity,
17088                window,
17089                move |_, _, event: &EditorEvent, _window, _cx| {
17090                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17091                        *is_still_following.borrow_mut() = false;
17092                    }
17093
17094                    if let EditorEvent::BufferEdited = event {
17095                        *follower_edit_event_count.borrow_mut() += 1;
17096                    }
17097                },
17098            )
17099            .detach();
17100        }
17101    });
17102
17103    // Update the selections only
17104    _ = leader.update(cx, |leader, window, cx| {
17105        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17106            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17107        });
17108    });
17109    follower
17110        .update(cx, |follower, window, cx| {
17111            follower.apply_update_proto(
17112                &project,
17113                pending_update.borrow_mut().take().unwrap(),
17114                window,
17115                cx,
17116            )
17117        })
17118        .unwrap()
17119        .await
17120        .unwrap();
17121    _ = follower.update(cx, |follower, _, cx| {
17122        assert_eq!(
17123            follower.selections.ranges(&follower.display_snapshot(cx)),
17124            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17125        );
17126    });
17127    assert!(*is_still_following.borrow());
17128    assert_eq!(*follower_edit_event_count.borrow(), 0);
17129
17130    // Update the scroll position only
17131    _ = leader.update(cx, |leader, window, cx| {
17132        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17133    });
17134    follower
17135        .update(cx, |follower, window, cx| {
17136            follower.apply_update_proto(
17137                &project,
17138                pending_update.borrow_mut().take().unwrap(),
17139                window,
17140                cx,
17141            )
17142        })
17143        .unwrap()
17144        .await
17145        .unwrap();
17146    assert_eq!(
17147        follower
17148            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17149            .unwrap(),
17150        gpui::Point::new(1.5, 3.5)
17151    );
17152    assert!(*is_still_following.borrow());
17153    assert_eq!(*follower_edit_event_count.borrow(), 0);
17154
17155    // Update the selections and scroll position. The follower's scroll position is updated
17156    // via autoscroll, not via the leader's exact scroll position.
17157    _ = leader.update(cx, |leader, window, cx| {
17158        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17159            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17160        });
17161        leader.request_autoscroll(Autoscroll::newest(), cx);
17162        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17163    });
17164    follower
17165        .update(cx, |follower, window, cx| {
17166            follower.apply_update_proto(
17167                &project,
17168                pending_update.borrow_mut().take().unwrap(),
17169                window,
17170                cx,
17171            )
17172        })
17173        .unwrap()
17174        .await
17175        .unwrap();
17176    _ = follower.update(cx, |follower, _, cx| {
17177        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17178        assert_eq!(
17179            follower.selections.ranges(&follower.display_snapshot(cx)),
17180            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17181        );
17182    });
17183    assert!(*is_still_following.borrow());
17184
17185    // Creating a pending selection that precedes another selection
17186    _ = leader.update(cx, |leader, window, cx| {
17187        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17188            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17189        });
17190        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17191    });
17192    follower
17193        .update(cx, |follower, window, cx| {
17194            follower.apply_update_proto(
17195                &project,
17196                pending_update.borrow_mut().take().unwrap(),
17197                window,
17198                cx,
17199            )
17200        })
17201        .unwrap()
17202        .await
17203        .unwrap();
17204    _ = follower.update(cx, |follower, _, cx| {
17205        assert_eq!(
17206            follower.selections.ranges(&follower.display_snapshot(cx)),
17207            vec![
17208                MultiBufferOffset(0)..MultiBufferOffset(0),
17209                MultiBufferOffset(1)..MultiBufferOffset(1)
17210            ]
17211        );
17212    });
17213    assert!(*is_still_following.borrow());
17214
17215    // Extend the pending selection so that it surrounds another selection
17216    _ = leader.update(cx, |leader, window, cx| {
17217        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17218    });
17219    follower
17220        .update(cx, |follower, window, cx| {
17221            follower.apply_update_proto(
17222                &project,
17223                pending_update.borrow_mut().take().unwrap(),
17224                window,
17225                cx,
17226            )
17227        })
17228        .unwrap()
17229        .await
17230        .unwrap();
17231    _ = follower.update(cx, |follower, _, cx| {
17232        assert_eq!(
17233            follower.selections.ranges(&follower.display_snapshot(cx)),
17234            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17235        );
17236    });
17237
17238    // Scrolling locally breaks the follow
17239    _ = follower.update(cx, |follower, window, cx| {
17240        let top_anchor = follower
17241            .buffer()
17242            .read(cx)
17243            .read(cx)
17244            .anchor_after(MultiBufferOffset(0));
17245        follower.set_scroll_anchor(
17246            ScrollAnchor {
17247                anchor: top_anchor,
17248                offset: gpui::Point::new(0.0, 0.5),
17249            },
17250            window,
17251            cx,
17252        );
17253    });
17254    assert!(!(*is_still_following.borrow()));
17255}
17256
17257#[gpui::test]
17258async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17259    init_test(cx, |_| {});
17260
17261    let fs = FakeFs::new(cx.executor());
17262    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17263    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17264    let pane = workspace
17265        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17266        .unwrap();
17267
17268    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17269
17270    let leader = pane.update_in(cx, |_, window, cx| {
17271        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17272        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17273    });
17274
17275    // Start following the editor when it has no excerpts.
17276    let mut state_message =
17277        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17278    let workspace_entity = workspace.root(cx).unwrap();
17279    let follower_1 = cx
17280        .update_window(*workspace.deref(), |_, window, cx| {
17281            Editor::from_state_proto(
17282                workspace_entity,
17283                ViewId {
17284                    creator: CollaboratorId::PeerId(PeerId::default()),
17285                    id: 0,
17286                },
17287                &mut state_message,
17288                window,
17289                cx,
17290            )
17291        })
17292        .unwrap()
17293        .unwrap()
17294        .await
17295        .unwrap();
17296
17297    let update_message = Rc::new(RefCell::new(None));
17298    follower_1.update_in(cx, {
17299        let update = update_message.clone();
17300        |_, window, cx| {
17301            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17302                leader.read(cx).add_event_to_update_proto(
17303                    event,
17304                    &mut update.borrow_mut(),
17305                    window,
17306                    cx,
17307                );
17308            })
17309            .detach();
17310        }
17311    });
17312
17313    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17314        (
17315            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17316            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17317        )
17318    });
17319
17320    // Insert some excerpts.
17321    leader.update(cx, |leader, cx| {
17322        leader.buffer.update(cx, |multibuffer, cx| {
17323            multibuffer.set_excerpts_for_path(
17324                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17325                buffer_1.clone(),
17326                vec![
17327                    Point::row_range(0..3),
17328                    Point::row_range(1..6),
17329                    Point::row_range(12..15),
17330                ],
17331                0,
17332                cx,
17333            );
17334            multibuffer.set_excerpts_for_path(
17335                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17336                buffer_2.clone(),
17337                vec![Point::row_range(0..6), Point::row_range(8..12)],
17338                0,
17339                cx,
17340            );
17341        });
17342    });
17343
17344    // Apply the update of adding the excerpts.
17345    follower_1
17346        .update_in(cx, |follower, window, cx| {
17347            follower.apply_update_proto(
17348                &project,
17349                update_message.borrow().clone().unwrap(),
17350                window,
17351                cx,
17352            )
17353        })
17354        .await
17355        .unwrap();
17356    assert_eq!(
17357        follower_1.update(cx, |editor, cx| editor.text(cx)),
17358        leader.update(cx, |editor, cx| editor.text(cx))
17359    );
17360    update_message.borrow_mut().take();
17361
17362    // Start following separately after it already has excerpts.
17363    let mut state_message =
17364        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17365    let workspace_entity = workspace.root(cx).unwrap();
17366    let follower_2 = cx
17367        .update_window(*workspace.deref(), |_, window, cx| {
17368            Editor::from_state_proto(
17369                workspace_entity,
17370                ViewId {
17371                    creator: CollaboratorId::PeerId(PeerId::default()),
17372                    id: 0,
17373                },
17374                &mut state_message,
17375                window,
17376                cx,
17377            )
17378        })
17379        .unwrap()
17380        .unwrap()
17381        .await
17382        .unwrap();
17383    assert_eq!(
17384        follower_2.update(cx, |editor, cx| editor.text(cx)),
17385        leader.update(cx, |editor, cx| editor.text(cx))
17386    );
17387
17388    // Remove some excerpts.
17389    leader.update(cx, |leader, cx| {
17390        leader.buffer.update(cx, |multibuffer, cx| {
17391            let excerpt_ids = multibuffer.excerpt_ids();
17392            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17393            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17394        });
17395    });
17396
17397    // Apply the update of removing the excerpts.
17398    follower_1
17399        .update_in(cx, |follower, window, cx| {
17400            follower.apply_update_proto(
17401                &project,
17402                update_message.borrow().clone().unwrap(),
17403                window,
17404                cx,
17405            )
17406        })
17407        .await
17408        .unwrap();
17409    follower_2
17410        .update_in(cx, |follower, window, cx| {
17411            follower.apply_update_proto(
17412                &project,
17413                update_message.borrow().clone().unwrap(),
17414                window,
17415                cx,
17416            )
17417        })
17418        .await
17419        .unwrap();
17420    update_message.borrow_mut().take();
17421    assert_eq!(
17422        follower_1.update(cx, |editor, cx| editor.text(cx)),
17423        leader.update(cx, |editor, cx| editor.text(cx))
17424    );
17425}
17426
17427#[gpui::test]
17428async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17429    init_test(cx, |_| {});
17430
17431    let mut cx = EditorTestContext::new(cx).await;
17432    let lsp_store =
17433        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17434
17435    cx.set_state(indoc! {"
17436        ˇfn func(abc def: i32) -> u32 {
17437        }
17438    "});
17439
17440    cx.update(|_, cx| {
17441        lsp_store.update(cx, |lsp_store, cx| {
17442            lsp_store
17443                .update_diagnostics(
17444                    LanguageServerId(0),
17445                    lsp::PublishDiagnosticsParams {
17446                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17447                        version: None,
17448                        diagnostics: vec![
17449                            lsp::Diagnostic {
17450                                range: lsp::Range::new(
17451                                    lsp::Position::new(0, 11),
17452                                    lsp::Position::new(0, 12),
17453                                ),
17454                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17455                                ..Default::default()
17456                            },
17457                            lsp::Diagnostic {
17458                                range: lsp::Range::new(
17459                                    lsp::Position::new(0, 12),
17460                                    lsp::Position::new(0, 15),
17461                                ),
17462                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17463                                ..Default::default()
17464                            },
17465                            lsp::Diagnostic {
17466                                range: lsp::Range::new(
17467                                    lsp::Position::new(0, 25),
17468                                    lsp::Position::new(0, 28),
17469                                ),
17470                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17471                                ..Default::default()
17472                            },
17473                        ],
17474                    },
17475                    None,
17476                    DiagnosticSourceKind::Pushed,
17477                    &[],
17478                    cx,
17479                )
17480                .unwrap()
17481        });
17482    });
17483
17484    executor.run_until_parked();
17485
17486    cx.update_editor(|editor, window, cx| {
17487        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17488    });
17489
17490    cx.assert_editor_state(indoc! {"
17491        fn func(abc def: i32) -> ˇu32 {
17492        }
17493    "});
17494
17495    cx.update_editor(|editor, window, cx| {
17496        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17497    });
17498
17499    cx.assert_editor_state(indoc! {"
17500        fn func(abc ˇdef: i32) -> u32 {
17501        }
17502    "});
17503
17504    cx.update_editor(|editor, window, cx| {
17505        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17506    });
17507
17508    cx.assert_editor_state(indoc! {"
17509        fn func(abcˇ def: i32) -> u32 {
17510        }
17511    "});
17512
17513    cx.update_editor(|editor, window, cx| {
17514        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17515    });
17516
17517    cx.assert_editor_state(indoc! {"
17518        fn func(abc def: i32) -> ˇu32 {
17519        }
17520    "});
17521}
17522
17523#[gpui::test]
17524async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17525    init_test(cx, |_| {});
17526
17527    let mut cx = EditorTestContext::new(cx).await;
17528
17529    let diff_base = r#"
17530        use some::mod;
17531
17532        const A: u32 = 42;
17533
17534        fn main() {
17535            println!("hello");
17536
17537            println!("world");
17538        }
17539        "#
17540    .unindent();
17541
17542    // Edits are modified, removed, modified, added
17543    cx.set_state(
17544        &r#"
17545        use some::modified;
17546
17547        ˇ
17548        fn main() {
17549            println!("hello there");
17550
17551            println!("around the");
17552            println!("world");
17553        }
17554        "#
17555        .unindent(),
17556    );
17557
17558    cx.set_head_text(&diff_base);
17559    executor.run_until_parked();
17560
17561    cx.update_editor(|editor, window, cx| {
17562        //Wrap around the bottom of the buffer
17563        for _ in 0..3 {
17564            editor.go_to_next_hunk(&GoToHunk, window, cx);
17565        }
17566    });
17567
17568    cx.assert_editor_state(
17569        &r#"
17570        ˇuse some::modified;
17571
17572
17573        fn main() {
17574            println!("hello there");
17575
17576            println!("around the");
17577            println!("world");
17578        }
17579        "#
17580        .unindent(),
17581    );
17582
17583    cx.update_editor(|editor, window, cx| {
17584        //Wrap around the top of the buffer
17585        for _ in 0..2 {
17586            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17587        }
17588    });
17589
17590    cx.assert_editor_state(
17591        &r#"
17592        use some::modified;
17593
17594
17595        fn main() {
17596        ˇ    println!("hello there");
17597
17598            println!("around the");
17599            println!("world");
17600        }
17601        "#
17602        .unindent(),
17603    );
17604
17605    cx.update_editor(|editor, window, cx| {
17606        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17607    });
17608
17609    cx.assert_editor_state(
17610        &r#"
17611        use some::modified;
17612
17613        ˇ
17614        fn main() {
17615            println!("hello there");
17616
17617            println!("around the");
17618            println!("world");
17619        }
17620        "#
17621        .unindent(),
17622    );
17623
17624    cx.update_editor(|editor, window, cx| {
17625        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17626    });
17627
17628    cx.assert_editor_state(
17629        &r#"
17630        ˇuse some::modified;
17631
17632
17633        fn main() {
17634            println!("hello there");
17635
17636            println!("around the");
17637            println!("world");
17638        }
17639        "#
17640        .unindent(),
17641    );
17642
17643    cx.update_editor(|editor, window, cx| {
17644        for _ in 0..2 {
17645            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17646        }
17647    });
17648
17649    cx.assert_editor_state(
17650        &r#"
17651        use some::modified;
17652
17653
17654        fn main() {
17655        ˇ    println!("hello there");
17656
17657            println!("around the");
17658            println!("world");
17659        }
17660        "#
17661        .unindent(),
17662    );
17663
17664    cx.update_editor(|editor, window, cx| {
17665        editor.fold(&Fold, window, cx);
17666    });
17667
17668    cx.update_editor(|editor, window, cx| {
17669        editor.go_to_next_hunk(&GoToHunk, window, cx);
17670    });
17671
17672    cx.assert_editor_state(
17673        &r#"
17674        ˇuse some::modified;
17675
17676
17677        fn main() {
17678            println!("hello there");
17679
17680            println!("around the");
17681            println!("world");
17682        }
17683        "#
17684        .unindent(),
17685    );
17686}
17687
17688#[test]
17689fn test_split_words() {
17690    fn split(text: &str) -> Vec<&str> {
17691        split_words(text).collect()
17692    }
17693
17694    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17695    assert_eq!(split("hello_world"), &["hello_", "world"]);
17696    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17697    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17698    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17699    assert_eq!(split("helloworld"), &["helloworld"]);
17700
17701    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17702}
17703
17704#[test]
17705fn test_split_words_for_snippet_prefix() {
17706    fn split(text: &str) -> Vec<&str> {
17707        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17708    }
17709
17710    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17711    assert_eq!(split("hello_world"), &["hello_world"]);
17712    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17713    assert_eq!(split("Hello_World"), &["Hello_World"]);
17714    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17715    assert_eq!(split("helloworld"), &["helloworld"]);
17716    assert_eq!(
17717        split("this@is!@#$^many   . symbols"),
17718        &[
17719            "symbols",
17720            " symbols",
17721            ". symbols",
17722            " . symbols",
17723            "  . symbols",
17724            "   . symbols",
17725            "many   . symbols",
17726            "^many   . symbols",
17727            "$^many   . symbols",
17728            "#$^many   . symbols",
17729            "@#$^many   . symbols",
17730            "!@#$^many   . symbols",
17731            "is!@#$^many   . symbols",
17732            "@is!@#$^many   . symbols",
17733            "this@is!@#$^many   . symbols",
17734        ],
17735    );
17736    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17737}
17738
17739#[gpui::test]
17740async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17741    init_test(cx, |_| {});
17742
17743    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17744
17745    #[track_caller]
17746    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17747        let _state_context = cx.set_state(before);
17748        cx.run_until_parked();
17749        cx.update_editor(|editor, window, cx| {
17750            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17751        });
17752        cx.run_until_parked();
17753        cx.assert_editor_state(after);
17754    }
17755
17756    // Outside bracket jumps to outside of matching bracket
17757    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17758    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17759
17760    // Inside bracket jumps to inside of matching bracket
17761    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17762    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17763
17764    // When outside a bracket and inside, favor jumping to the inside bracket
17765    assert(
17766        "console.log('foo', [1, 2, 3]ˇ);",
17767        "console.log('foo', ˇ[1, 2, 3]);",
17768        &mut cx,
17769    );
17770    assert(
17771        "console.log(ˇ'foo', [1, 2, 3]);",
17772        "console.log('foo'ˇ, [1, 2, 3]);",
17773        &mut cx,
17774    );
17775
17776    // Bias forward if two options are equally likely
17777    assert(
17778        "let result = curried_fun()ˇ();",
17779        "let result = curried_fun()()ˇ;",
17780        &mut cx,
17781    );
17782
17783    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17784    assert(
17785        indoc! {"
17786            function test() {
17787                console.log('test')ˇ
17788            }"},
17789        indoc! {"
17790            function test() {
17791                console.logˇ('test')
17792            }"},
17793        &mut cx,
17794    );
17795}
17796
17797#[gpui::test]
17798async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
17799    init_test(cx, |_| {});
17800    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
17801    language_registry.add(markdown_lang());
17802    language_registry.add(rust_lang());
17803    let buffer = cx.new(|cx| {
17804        let mut buffer = language::Buffer::local(
17805            indoc! {"
17806            ```rs
17807            impl Worktree {
17808                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17809                }
17810            }
17811            ```
17812        "},
17813            cx,
17814        );
17815        buffer.set_language_registry(language_registry.clone());
17816        buffer.set_language(Some(markdown_lang()), cx);
17817        buffer
17818    });
17819    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17820    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17821    cx.executor().run_until_parked();
17822    _ = editor.update(cx, |editor, window, cx| {
17823        // Case 1: Test outer enclosing brackets
17824        select_ranges(
17825            editor,
17826            &indoc! {"
17827                ```rs
17828                impl Worktree {
17829                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17830                    }
1783117832                ```
17833            "},
17834            window,
17835            cx,
17836        );
17837        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17838        assert_text_with_selections(
17839            editor,
17840            &indoc! {"
17841                ```rs
17842                impl Worktree ˇ{
17843                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17844                    }
17845                }
17846                ```
17847            "},
17848            cx,
17849        );
17850        // Case 2: Test inner enclosing brackets
17851        select_ranges(
17852            editor,
17853            &indoc! {"
17854                ```rs
17855                impl Worktree {
17856                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1785717858                }
17859                ```
17860            "},
17861            window,
17862            cx,
17863        );
17864        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17865        assert_text_with_selections(
17866            editor,
17867            &indoc! {"
17868                ```rs
17869                impl Worktree {
17870                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
17871                    }
17872                }
17873                ```
17874            "},
17875            cx,
17876        );
17877    });
17878}
17879
17880#[gpui::test]
17881async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17882    init_test(cx, |_| {});
17883
17884    let fs = FakeFs::new(cx.executor());
17885    fs.insert_tree(
17886        path!("/a"),
17887        json!({
17888            "main.rs": "fn main() { let a = 5; }",
17889            "other.rs": "// Test file",
17890        }),
17891    )
17892    .await;
17893    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17894
17895    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17896    language_registry.add(Arc::new(Language::new(
17897        LanguageConfig {
17898            name: "Rust".into(),
17899            matcher: LanguageMatcher {
17900                path_suffixes: vec!["rs".to_string()],
17901                ..Default::default()
17902            },
17903            brackets: BracketPairConfig {
17904                pairs: vec![BracketPair {
17905                    start: "{".to_string(),
17906                    end: "}".to_string(),
17907                    close: true,
17908                    surround: true,
17909                    newline: true,
17910                }],
17911                disabled_scopes_by_bracket_ix: Vec::new(),
17912            },
17913            ..Default::default()
17914        },
17915        Some(tree_sitter_rust::LANGUAGE.into()),
17916    )));
17917    let mut fake_servers = language_registry.register_fake_lsp(
17918        "Rust",
17919        FakeLspAdapter {
17920            capabilities: lsp::ServerCapabilities {
17921                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17922                    first_trigger_character: "{".to_string(),
17923                    more_trigger_character: None,
17924                }),
17925                ..Default::default()
17926            },
17927            ..Default::default()
17928        },
17929    );
17930
17931    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17932
17933    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17934
17935    let worktree_id = workspace
17936        .update(cx, |workspace, _, cx| {
17937            workspace.project().update(cx, |project, cx| {
17938                project.worktrees(cx).next().unwrap().read(cx).id()
17939            })
17940        })
17941        .unwrap();
17942
17943    let buffer = project
17944        .update(cx, |project, cx| {
17945            project.open_local_buffer(path!("/a/main.rs"), cx)
17946        })
17947        .await
17948        .unwrap();
17949    let editor_handle = workspace
17950        .update(cx, |workspace, window, cx| {
17951            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17952        })
17953        .unwrap()
17954        .await
17955        .unwrap()
17956        .downcast::<Editor>()
17957        .unwrap();
17958
17959    cx.executor().start_waiting();
17960    let fake_server = fake_servers.next().await.unwrap();
17961
17962    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17963        |params, _| async move {
17964            assert_eq!(
17965                params.text_document_position.text_document.uri,
17966                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17967            );
17968            assert_eq!(
17969                params.text_document_position.position,
17970                lsp::Position::new(0, 21),
17971            );
17972
17973            Ok(Some(vec![lsp::TextEdit {
17974                new_text: "]".to_string(),
17975                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17976            }]))
17977        },
17978    );
17979
17980    editor_handle.update_in(cx, |editor, window, cx| {
17981        window.focus(&editor.focus_handle(cx));
17982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17983            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17984        });
17985        editor.handle_input("{", window, cx);
17986    });
17987
17988    cx.executor().run_until_parked();
17989
17990    buffer.update(cx, |buffer, _| {
17991        assert_eq!(
17992            buffer.text(),
17993            "fn main() { let a = {5}; }",
17994            "No extra braces from on type formatting should appear in the buffer"
17995        )
17996    });
17997}
17998
17999#[gpui::test(iterations = 20, seeds(31))]
18000async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18001    init_test(cx, |_| {});
18002
18003    let mut cx = EditorLspTestContext::new_rust(
18004        lsp::ServerCapabilities {
18005            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18006                first_trigger_character: ".".to_string(),
18007                more_trigger_character: None,
18008            }),
18009            ..Default::default()
18010        },
18011        cx,
18012    )
18013    .await;
18014
18015    cx.update_buffer(|buffer, _| {
18016        // This causes autoindent to be async.
18017        buffer.set_sync_parse_timeout(Duration::ZERO)
18018    });
18019
18020    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18021    cx.simulate_keystroke("\n");
18022    cx.run_until_parked();
18023
18024    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18025    let mut request =
18026        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18027            let buffer_cloned = buffer_cloned.clone();
18028            async move {
18029                buffer_cloned.update(&mut cx, |buffer, _| {
18030                    assert_eq!(
18031                        buffer.text(),
18032                        "fn c() {\n    d()\n        .\n}\n",
18033                        "OnTypeFormatting should triggered after autoindent applied"
18034                    )
18035                })?;
18036
18037                Ok(Some(vec![]))
18038            }
18039        });
18040
18041    cx.simulate_keystroke(".");
18042    cx.run_until_parked();
18043
18044    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18045    assert!(request.next().await.is_some());
18046    request.close();
18047    assert!(request.next().await.is_none());
18048}
18049
18050#[gpui::test]
18051async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18052    init_test(cx, |_| {});
18053
18054    let fs = FakeFs::new(cx.executor());
18055    fs.insert_tree(
18056        path!("/a"),
18057        json!({
18058            "main.rs": "fn main() { let a = 5; }",
18059            "other.rs": "// Test file",
18060        }),
18061    )
18062    .await;
18063
18064    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18065
18066    let server_restarts = Arc::new(AtomicUsize::new(0));
18067    let closure_restarts = Arc::clone(&server_restarts);
18068    let language_server_name = "test language server";
18069    let language_name: LanguageName = "Rust".into();
18070
18071    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18072    language_registry.add(Arc::new(Language::new(
18073        LanguageConfig {
18074            name: language_name.clone(),
18075            matcher: LanguageMatcher {
18076                path_suffixes: vec!["rs".to_string()],
18077                ..Default::default()
18078            },
18079            ..Default::default()
18080        },
18081        Some(tree_sitter_rust::LANGUAGE.into()),
18082    )));
18083    let mut fake_servers = language_registry.register_fake_lsp(
18084        "Rust",
18085        FakeLspAdapter {
18086            name: language_server_name,
18087            initialization_options: Some(json!({
18088                "testOptionValue": true
18089            })),
18090            initializer: Some(Box::new(move |fake_server| {
18091                let task_restarts = Arc::clone(&closure_restarts);
18092                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18093                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18094                    futures::future::ready(Ok(()))
18095                });
18096            })),
18097            ..Default::default()
18098        },
18099    );
18100
18101    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18102    let _buffer = project
18103        .update(cx, |project, cx| {
18104            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18105        })
18106        .await
18107        .unwrap();
18108    let _fake_server = fake_servers.next().await.unwrap();
18109    update_test_language_settings(cx, |language_settings| {
18110        language_settings.languages.0.insert(
18111            language_name.clone().0,
18112            LanguageSettingsContent {
18113                tab_size: NonZeroU32::new(8),
18114                ..Default::default()
18115            },
18116        );
18117    });
18118    cx.executor().run_until_parked();
18119    assert_eq!(
18120        server_restarts.load(atomic::Ordering::Acquire),
18121        0,
18122        "Should not restart LSP server on an unrelated change"
18123    );
18124
18125    update_test_project_settings(cx, |project_settings| {
18126        project_settings.lsp.insert(
18127            "Some other server name".into(),
18128            LspSettings {
18129                binary: None,
18130                settings: None,
18131                initialization_options: Some(json!({
18132                    "some other init value": false
18133                })),
18134                enable_lsp_tasks: false,
18135                fetch: None,
18136            },
18137        );
18138    });
18139    cx.executor().run_until_parked();
18140    assert_eq!(
18141        server_restarts.load(atomic::Ordering::Acquire),
18142        0,
18143        "Should not restart LSP server on an unrelated LSP settings change"
18144    );
18145
18146    update_test_project_settings(cx, |project_settings| {
18147        project_settings.lsp.insert(
18148            language_server_name.into(),
18149            LspSettings {
18150                binary: None,
18151                settings: None,
18152                initialization_options: Some(json!({
18153                    "anotherInitValue": false
18154                })),
18155                enable_lsp_tasks: false,
18156                fetch: None,
18157            },
18158        );
18159    });
18160    cx.executor().run_until_parked();
18161    assert_eq!(
18162        server_restarts.load(atomic::Ordering::Acquire),
18163        1,
18164        "Should restart LSP server on a related LSP settings change"
18165    );
18166
18167    update_test_project_settings(cx, |project_settings| {
18168        project_settings.lsp.insert(
18169            language_server_name.into(),
18170            LspSettings {
18171                binary: None,
18172                settings: None,
18173                initialization_options: Some(json!({
18174                    "anotherInitValue": false
18175                })),
18176                enable_lsp_tasks: false,
18177                fetch: None,
18178            },
18179        );
18180    });
18181    cx.executor().run_until_parked();
18182    assert_eq!(
18183        server_restarts.load(atomic::Ordering::Acquire),
18184        1,
18185        "Should not restart LSP server on a related LSP settings change that is the same"
18186    );
18187
18188    update_test_project_settings(cx, |project_settings| {
18189        project_settings.lsp.insert(
18190            language_server_name.into(),
18191            LspSettings {
18192                binary: None,
18193                settings: None,
18194                initialization_options: None,
18195                enable_lsp_tasks: false,
18196                fetch: None,
18197            },
18198        );
18199    });
18200    cx.executor().run_until_parked();
18201    assert_eq!(
18202        server_restarts.load(atomic::Ordering::Acquire),
18203        2,
18204        "Should restart LSP server on another related LSP settings change"
18205    );
18206}
18207
18208#[gpui::test]
18209async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18210    init_test(cx, |_| {});
18211
18212    let mut cx = EditorLspTestContext::new_rust(
18213        lsp::ServerCapabilities {
18214            completion_provider: Some(lsp::CompletionOptions {
18215                trigger_characters: Some(vec![".".to_string()]),
18216                resolve_provider: Some(true),
18217                ..Default::default()
18218            }),
18219            ..Default::default()
18220        },
18221        cx,
18222    )
18223    .await;
18224
18225    cx.set_state("fn main() { let a = 2ˇ; }");
18226    cx.simulate_keystroke(".");
18227    let completion_item = lsp::CompletionItem {
18228        label: "some".into(),
18229        kind: Some(lsp::CompletionItemKind::SNIPPET),
18230        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18231        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18232            kind: lsp::MarkupKind::Markdown,
18233            value: "```rust\nSome(2)\n```".to_string(),
18234        })),
18235        deprecated: Some(false),
18236        sort_text: Some("fffffff2".to_string()),
18237        filter_text: Some("some".to_string()),
18238        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18239        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18240            range: lsp::Range {
18241                start: lsp::Position {
18242                    line: 0,
18243                    character: 22,
18244                },
18245                end: lsp::Position {
18246                    line: 0,
18247                    character: 22,
18248                },
18249            },
18250            new_text: "Some(2)".to_string(),
18251        })),
18252        additional_text_edits: Some(vec![lsp::TextEdit {
18253            range: lsp::Range {
18254                start: lsp::Position {
18255                    line: 0,
18256                    character: 20,
18257                },
18258                end: lsp::Position {
18259                    line: 0,
18260                    character: 22,
18261                },
18262            },
18263            new_text: "".to_string(),
18264        }]),
18265        ..Default::default()
18266    };
18267
18268    let closure_completion_item = completion_item.clone();
18269    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18270        let task_completion_item = closure_completion_item.clone();
18271        async move {
18272            Ok(Some(lsp::CompletionResponse::Array(vec![
18273                task_completion_item,
18274            ])))
18275        }
18276    });
18277
18278    request.next().await;
18279
18280    cx.condition(|editor, _| editor.context_menu_visible())
18281        .await;
18282    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18283        editor
18284            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18285            .unwrap()
18286    });
18287    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18288
18289    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18290        let task_completion_item = completion_item.clone();
18291        async move { Ok(task_completion_item) }
18292    })
18293    .next()
18294    .await
18295    .unwrap();
18296    apply_additional_edits.await.unwrap();
18297    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18298}
18299
18300#[gpui::test]
18301async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18302    init_test(cx, |_| {});
18303
18304    let mut cx = EditorLspTestContext::new_rust(
18305        lsp::ServerCapabilities {
18306            completion_provider: Some(lsp::CompletionOptions {
18307                trigger_characters: Some(vec![".".to_string()]),
18308                resolve_provider: Some(true),
18309                ..Default::default()
18310            }),
18311            ..Default::default()
18312        },
18313        cx,
18314    )
18315    .await;
18316
18317    cx.set_state("fn main() { let a = 2ˇ; }");
18318    cx.simulate_keystroke(".");
18319
18320    let item1 = lsp::CompletionItem {
18321        label: "method id()".to_string(),
18322        filter_text: Some("id".to_string()),
18323        detail: None,
18324        documentation: None,
18325        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18326            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18327            new_text: ".id".to_string(),
18328        })),
18329        ..lsp::CompletionItem::default()
18330    };
18331
18332    let item2 = lsp::CompletionItem {
18333        label: "other".to_string(),
18334        filter_text: Some("other".to_string()),
18335        detail: None,
18336        documentation: None,
18337        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18338            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18339            new_text: ".other".to_string(),
18340        })),
18341        ..lsp::CompletionItem::default()
18342    };
18343
18344    let item1 = item1.clone();
18345    cx.set_request_handler::<lsp::request::Completion, _, _>({
18346        let item1 = item1.clone();
18347        move |_, _, _| {
18348            let item1 = item1.clone();
18349            let item2 = item2.clone();
18350            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18351        }
18352    })
18353    .next()
18354    .await;
18355
18356    cx.condition(|editor, _| editor.context_menu_visible())
18357        .await;
18358    cx.update_editor(|editor, _, _| {
18359        let context_menu = editor.context_menu.borrow_mut();
18360        let context_menu = context_menu
18361            .as_ref()
18362            .expect("Should have the context menu deployed");
18363        match context_menu {
18364            CodeContextMenu::Completions(completions_menu) => {
18365                let completions = completions_menu.completions.borrow_mut();
18366                assert_eq!(
18367                    completions
18368                        .iter()
18369                        .map(|completion| &completion.label.text)
18370                        .collect::<Vec<_>>(),
18371                    vec!["method id()", "other"]
18372                )
18373            }
18374            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18375        }
18376    });
18377
18378    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18379        let item1 = item1.clone();
18380        move |_, item_to_resolve, _| {
18381            let item1 = item1.clone();
18382            async move {
18383                if item1 == item_to_resolve {
18384                    Ok(lsp::CompletionItem {
18385                        label: "method id()".to_string(),
18386                        filter_text: Some("id".to_string()),
18387                        detail: Some("Now resolved!".to_string()),
18388                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18389                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18390                            range: lsp::Range::new(
18391                                lsp::Position::new(0, 22),
18392                                lsp::Position::new(0, 22),
18393                            ),
18394                            new_text: ".id".to_string(),
18395                        })),
18396                        ..lsp::CompletionItem::default()
18397                    })
18398                } else {
18399                    Ok(item_to_resolve)
18400                }
18401            }
18402        }
18403    })
18404    .next()
18405    .await
18406    .unwrap();
18407    cx.run_until_parked();
18408
18409    cx.update_editor(|editor, window, cx| {
18410        editor.context_menu_next(&Default::default(), window, cx);
18411    });
18412
18413    cx.update_editor(|editor, _, _| {
18414        let context_menu = editor.context_menu.borrow_mut();
18415        let context_menu = context_menu
18416            .as_ref()
18417            .expect("Should have the context menu deployed");
18418        match context_menu {
18419            CodeContextMenu::Completions(completions_menu) => {
18420                let completions = completions_menu.completions.borrow_mut();
18421                assert_eq!(
18422                    completions
18423                        .iter()
18424                        .map(|completion| &completion.label.text)
18425                        .collect::<Vec<_>>(),
18426                    vec!["method id() Now resolved!", "other"],
18427                    "Should update first completion label, but not second as the filter text did not match."
18428                );
18429            }
18430            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18431        }
18432    });
18433}
18434
18435#[gpui::test]
18436async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18437    init_test(cx, |_| {});
18438    let mut cx = EditorLspTestContext::new_rust(
18439        lsp::ServerCapabilities {
18440            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18441            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18442            completion_provider: Some(lsp::CompletionOptions {
18443                resolve_provider: Some(true),
18444                ..Default::default()
18445            }),
18446            ..Default::default()
18447        },
18448        cx,
18449    )
18450    .await;
18451    cx.set_state(indoc! {"
18452        struct TestStruct {
18453            field: i32
18454        }
18455
18456        fn mainˇ() {
18457            let unused_var = 42;
18458            let test_struct = TestStruct { field: 42 };
18459        }
18460    "});
18461    let symbol_range = cx.lsp_range(indoc! {"
18462        struct TestStruct {
18463            field: i32
18464        }
18465
18466        «fn main»() {
18467            let unused_var = 42;
18468            let test_struct = TestStruct { field: 42 };
18469        }
18470    "});
18471    let mut hover_requests =
18472        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18473            Ok(Some(lsp::Hover {
18474                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18475                    kind: lsp::MarkupKind::Markdown,
18476                    value: "Function documentation".to_string(),
18477                }),
18478                range: Some(symbol_range),
18479            }))
18480        });
18481
18482    // Case 1: Test that code action menu hide hover popover
18483    cx.dispatch_action(Hover);
18484    hover_requests.next().await;
18485    cx.condition(|editor, _| editor.hover_state.visible()).await;
18486    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18487        move |_, _, _| async move {
18488            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18489                lsp::CodeAction {
18490                    title: "Remove unused variable".to_string(),
18491                    kind: Some(CodeActionKind::QUICKFIX),
18492                    edit: Some(lsp::WorkspaceEdit {
18493                        changes: Some(
18494                            [(
18495                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18496                                vec![lsp::TextEdit {
18497                                    range: lsp::Range::new(
18498                                        lsp::Position::new(5, 4),
18499                                        lsp::Position::new(5, 27),
18500                                    ),
18501                                    new_text: "".to_string(),
18502                                }],
18503                            )]
18504                            .into_iter()
18505                            .collect(),
18506                        ),
18507                        ..Default::default()
18508                    }),
18509                    ..Default::default()
18510                },
18511            )]))
18512        },
18513    );
18514    cx.update_editor(|editor, window, cx| {
18515        editor.toggle_code_actions(
18516            &ToggleCodeActions {
18517                deployed_from: None,
18518                quick_launch: false,
18519            },
18520            window,
18521            cx,
18522        );
18523    });
18524    code_action_requests.next().await;
18525    cx.run_until_parked();
18526    cx.condition(|editor, _| editor.context_menu_visible())
18527        .await;
18528    cx.update_editor(|editor, _, _| {
18529        assert!(
18530            !editor.hover_state.visible(),
18531            "Hover popover should be hidden when code action menu is shown"
18532        );
18533        // Hide code actions
18534        editor.context_menu.take();
18535    });
18536
18537    // Case 2: Test that code completions hide hover popover
18538    cx.dispatch_action(Hover);
18539    hover_requests.next().await;
18540    cx.condition(|editor, _| editor.hover_state.visible()).await;
18541    let counter = Arc::new(AtomicUsize::new(0));
18542    let mut completion_requests =
18543        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18544            let counter = counter.clone();
18545            async move {
18546                counter.fetch_add(1, atomic::Ordering::Release);
18547                Ok(Some(lsp::CompletionResponse::Array(vec![
18548                    lsp::CompletionItem {
18549                        label: "main".into(),
18550                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18551                        detail: Some("() -> ()".to_string()),
18552                        ..Default::default()
18553                    },
18554                    lsp::CompletionItem {
18555                        label: "TestStruct".into(),
18556                        kind: Some(lsp::CompletionItemKind::STRUCT),
18557                        detail: Some("struct TestStruct".to_string()),
18558                        ..Default::default()
18559                    },
18560                ])))
18561            }
18562        });
18563    cx.update_editor(|editor, window, cx| {
18564        editor.show_completions(&ShowCompletions, window, cx);
18565    });
18566    completion_requests.next().await;
18567    cx.condition(|editor, _| editor.context_menu_visible())
18568        .await;
18569    cx.update_editor(|editor, _, _| {
18570        assert!(
18571            !editor.hover_state.visible(),
18572            "Hover popover should be hidden when completion menu is shown"
18573        );
18574    });
18575}
18576
18577#[gpui::test]
18578async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18579    init_test(cx, |_| {});
18580
18581    let mut cx = EditorLspTestContext::new_rust(
18582        lsp::ServerCapabilities {
18583            completion_provider: Some(lsp::CompletionOptions {
18584                trigger_characters: Some(vec![".".to_string()]),
18585                resolve_provider: Some(true),
18586                ..Default::default()
18587            }),
18588            ..Default::default()
18589        },
18590        cx,
18591    )
18592    .await;
18593
18594    cx.set_state("fn main() { let a = 2ˇ; }");
18595    cx.simulate_keystroke(".");
18596
18597    let unresolved_item_1 = lsp::CompletionItem {
18598        label: "id".to_string(),
18599        filter_text: Some("id".to_string()),
18600        detail: None,
18601        documentation: None,
18602        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18603            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18604            new_text: ".id".to_string(),
18605        })),
18606        ..lsp::CompletionItem::default()
18607    };
18608    let resolved_item_1 = lsp::CompletionItem {
18609        additional_text_edits: Some(vec![lsp::TextEdit {
18610            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18611            new_text: "!!".to_string(),
18612        }]),
18613        ..unresolved_item_1.clone()
18614    };
18615    let unresolved_item_2 = lsp::CompletionItem {
18616        label: "other".to_string(),
18617        filter_text: Some("other".to_string()),
18618        detail: None,
18619        documentation: None,
18620        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18621            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18622            new_text: ".other".to_string(),
18623        })),
18624        ..lsp::CompletionItem::default()
18625    };
18626    let resolved_item_2 = lsp::CompletionItem {
18627        additional_text_edits: Some(vec![lsp::TextEdit {
18628            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18629            new_text: "??".to_string(),
18630        }]),
18631        ..unresolved_item_2.clone()
18632    };
18633
18634    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18635    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18636    cx.lsp
18637        .server
18638        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18639            let unresolved_item_1 = unresolved_item_1.clone();
18640            let resolved_item_1 = resolved_item_1.clone();
18641            let unresolved_item_2 = unresolved_item_2.clone();
18642            let resolved_item_2 = resolved_item_2.clone();
18643            let resolve_requests_1 = resolve_requests_1.clone();
18644            let resolve_requests_2 = resolve_requests_2.clone();
18645            move |unresolved_request, _| {
18646                let unresolved_item_1 = unresolved_item_1.clone();
18647                let resolved_item_1 = resolved_item_1.clone();
18648                let unresolved_item_2 = unresolved_item_2.clone();
18649                let resolved_item_2 = resolved_item_2.clone();
18650                let resolve_requests_1 = resolve_requests_1.clone();
18651                let resolve_requests_2 = resolve_requests_2.clone();
18652                async move {
18653                    if unresolved_request == unresolved_item_1 {
18654                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18655                        Ok(resolved_item_1.clone())
18656                    } else if unresolved_request == unresolved_item_2 {
18657                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18658                        Ok(resolved_item_2.clone())
18659                    } else {
18660                        panic!("Unexpected completion item {unresolved_request:?}")
18661                    }
18662                }
18663            }
18664        })
18665        .detach();
18666
18667    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18668        let unresolved_item_1 = unresolved_item_1.clone();
18669        let unresolved_item_2 = unresolved_item_2.clone();
18670        async move {
18671            Ok(Some(lsp::CompletionResponse::Array(vec![
18672                unresolved_item_1,
18673                unresolved_item_2,
18674            ])))
18675        }
18676    })
18677    .next()
18678    .await;
18679
18680    cx.condition(|editor, _| editor.context_menu_visible())
18681        .await;
18682    cx.update_editor(|editor, _, _| {
18683        let context_menu = editor.context_menu.borrow_mut();
18684        let context_menu = context_menu
18685            .as_ref()
18686            .expect("Should have the context menu deployed");
18687        match context_menu {
18688            CodeContextMenu::Completions(completions_menu) => {
18689                let completions = completions_menu.completions.borrow_mut();
18690                assert_eq!(
18691                    completions
18692                        .iter()
18693                        .map(|completion| &completion.label.text)
18694                        .collect::<Vec<_>>(),
18695                    vec!["id", "other"]
18696                )
18697            }
18698            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18699        }
18700    });
18701    cx.run_until_parked();
18702
18703    cx.update_editor(|editor, window, cx| {
18704        editor.context_menu_next(&ContextMenuNext, window, cx);
18705    });
18706    cx.run_until_parked();
18707    cx.update_editor(|editor, window, cx| {
18708        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18709    });
18710    cx.run_until_parked();
18711    cx.update_editor(|editor, window, cx| {
18712        editor.context_menu_next(&ContextMenuNext, window, cx);
18713    });
18714    cx.run_until_parked();
18715    cx.update_editor(|editor, window, cx| {
18716        editor
18717            .compose_completion(&ComposeCompletion::default(), window, cx)
18718            .expect("No task returned")
18719    })
18720    .await
18721    .expect("Completion failed");
18722    cx.run_until_parked();
18723
18724    cx.update_editor(|editor, _, cx| {
18725        assert_eq!(
18726            resolve_requests_1.load(atomic::Ordering::Acquire),
18727            1,
18728            "Should always resolve once despite multiple selections"
18729        );
18730        assert_eq!(
18731            resolve_requests_2.load(atomic::Ordering::Acquire),
18732            1,
18733            "Should always resolve once after multiple selections and applying the completion"
18734        );
18735        assert_eq!(
18736            editor.text(cx),
18737            "fn main() { let a = ??.other; }",
18738            "Should use resolved data when applying the completion"
18739        );
18740    });
18741}
18742
18743#[gpui::test]
18744async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18745    init_test(cx, |_| {});
18746
18747    let item_0 = lsp::CompletionItem {
18748        label: "abs".into(),
18749        insert_text: Some("abs".into()),
18750        data: Some(json!({ "very": "special"})),
18751        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18752        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18753            lsp::InsertReplaceEdit {
18754                new_text: "abs".to_string(),
18755                insert: lsp::Range::default(),
18756                replace: lsp::Range::default(),
18757            },
18758        )),
18759        ..lsp::CompletionItem::default()
18760    };
18761    let items = iter::once(item_0.clone())
18762        .chain((11..51).map(|i| lsp::CompletionItem {
18763            label: format!("item_{}", i),
18764            insert_text: Some(format!("item_{}", i)),
18765            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18766            ..lsp::CompletionItem::default()
18767        }))
18768        .collect::<Vec<_>>();
18769
18770    let default_commit_characters = vec!["?".to_string()];
18771    let default_data = json!({ "default": "data"});
18772    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18773    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18774    let default_edit_range = lsp::Range {
18775        start: lsp::Position {
18776            line: 0,
18777            character: 5,
18778        },
18779        end: lsp::Position {
18780            line: 0,
18781            character: 5,
18782        },
18783    };
18784
18785    let mut cx = EditorLspTestContext::new_rust(
18786        lsp::ServerCapabilities {
18787            completion_provider: Some(lsp::CompletionOptions {
18788                trigger_characters: Some(vec![".".to_string()]),
18789                resolve_provider: Some(true),
18790                ..Default::default()
18791            }),
18792            ..Default::default()
18793        },
18794        cx,
18795    )
18796    .await;
18797
18798    cx.set_state("fn main() { let a = 2ˇ; }");
18799    cx.simulate_keystroke(".");
18800
18801    let completion_data = default_data.clone();
18802    let completion_characters = default_commit_characters.clone();
18803    let completion_items = items.clone();
18804    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18805        let default_data = completion_data.clone();
18806        let default_commit_characters = completion_characters.clone();
18807        let items = completion_items.clone();
18808        async move {
18809            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18810                items,
18811                item_defaults: Some(lsp::CompletionListItemDefaults {
18812                    data: Some(default_data.clone()),
18813                    commit_characters: Some(default_commit_characters.clone()),
18814                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18815                        default_edit_range,
18816                    )),
18817                    insert_text_format: Some(default_insert_text_format),
18818                    insert_text_mode: Some(default_insert_text_mode),
18819                }),
18820                ..lsp::CompletionList::default()
18821            })))
18822        }
18823    })
18824    .next()
18825    .await;
18826
18827    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18828    cx.lsp
18829        .server
18830        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18831            let closure_resolved_items = resolved_items.clone();
18832            move |item_to_resolve, _| {
18833                let closure_resolved_items = closure_resolved_items.clone();
18834                async move {
18835                    closure_resolved_items.lock().push(item_to_resolve.clone());
18836                    Ok(item_to_resolve)
18837                }
18838            }
18839        })
18840        .detach();
18841
18842    cx.condition(|editor, _| editor.context_menu_visible())
18843        .await;
18844    cx.run_until_parked();
18845    cx.update_editor(|editor, _, _| {
18846        let menu = editor.context_menu.borrow_mut();
18847        match menu.as_ref().expect("should have the completions menu") {
18848            CodeContextMenu::Completions(completions_menu) => {
18849                assert_eq!(
18850                    completions_menu
18851                        .entries
18852                        .borrow()
18853                        .iter()
18854                        .map(|mat| mat.string.clone())
18855                        .collect::<Vec<String>>(),
18856                    items
18857                        .iter()
18858                        .map(|completion| completion.label.clone())
18859                        .collect::<Vec<String>>()
18860                );
18861            }
18862            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18863        }
18864    });
18865    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18866    // with 4 from the end.
18867    assert_eq!(
18868        *resolved_items.lock(),
18869        [&items[0..16], &items[items.len() - 4..items.len()]]
18870            .concat()
18871            .iter()
18872            .cloned()
18873            .map(|mut item| {
18874                if item.data.is_none() {
18875                    item.data = Some(default_data.clone());
18876                }
18877                item
18878            })
18879            .collect::<Vec<lsp::CompletionItem>>(),
18880        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18881    );
18882    resolved_items.lock().clear();
18883
18884    cx.update_editor(|editor, window, cx| {
18885        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18886    });
18887    cx.run_until_parked();
18888    // Completions that have already been resolved are skipped.
18889    assert_eq!(
18890        *resolved_items.lock(),
18891        items[items.len() - 17..items.len() - 4]
18892            .iter()
18893            .cloned()
18894            .map(|mut item| {
18895                if item.data.is_none() {
18896                    item.data = Some(default_data.clone());
18897                }
18898                item
18899            })
18900            .collect::<Vec<lsp::CompletionItem>>()
18901    );
18902    resolved_items.lock().clear();
18903}
18904
18905#[gpui::test]
18906async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18907    init_test(cx, |_| {});
18908
18909    let mut cx = EditorLspTestContext::new(
18910        Language::new(
18911            LanguageConfig {
18912                matcher: LanguageMatcher {
18913                    path_suffixes: vec!["jsx".into()],
18914                    ..Default::default()
18915                },
18916                overrides: [(
18917                    "element".into(),
18918                    LanguageConfigOverride {
18919                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18920                        ..Default::default()
18921                    },
18922                )]
18923                .into_iter()
18924                .collect(),
18925                ..Default::default()
18926            },
18927            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18928        )
18929        .with_override_query("(jsx_self_closing_element) @element")
18930        .unwrap(),
18931        lsp::ServerCapabilities {
18932            completion_provider: Some(lsp::CompletionOptions {
18933                trigger_characters: Some(vec![":".to_string()]),
18934                ..Default::default()
18935            }),
18936            ..Default::default()
18937        },
18938        cx,
18939    )
18940    .await;
18941
18942    cx.lsp
18943        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18944            Ok(Some(lsp::CompletionResponse::Array(vec![
18945                lsp::CompletionItem {
18946                    label: "bg-blue".into(),
18947                    ..Default::default()
18948                },
18949                lsp::CompletionItem {
18950                    label: "bg-red".into(),
18951                    ..Default::default()
18952                },
18953                lsp::CompletionItem {
18954                    label: "bg-yellow".into(),
18955                    ..Default::default()
18956                },
18957            ])))
18958        });
18959
18960    cx.set_state(r#"<p class="bgˇ" />"#);
18961
18962    // Trigger completion when typing a dash, because the dash is an extra
18963    // word character in the 'element' scope, which contains the cursor.
18964    cx.simulate_keystroke("-");
18965    cx.executor().run_until_parked();
18966    cx.update_editor(|editor, _, _| {
18967        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18968        {
18969            assert_eq!(
18970                completion_menu_entries(menu),
18971                &["bg-blue", "bg-red", "bg-yellow"]
18972            );
18973        } else {
18974            panic!("expected completion menu to be open");
18975        }
18976    });
18977
18978    cx.simulate_keystroke("l");
18979    cx.executor().run_until_parked();
18980    cx.update_editor(|editor, _, _| {
18981        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18982        {
18983            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18984        } else {
18985            panic!("expected completion menu to be open");
18986        }
18987    });
18988
18989    // When filtering completions, consider the character after the '-' to
18990    // be the start of a subword.
18991    cx.set_state(r#"<p class="yelˇ" />"#);
18992    cx.simulate_keystroke("l");
18993    cx.executor().run_until_parked();
18994    cx.update_editor(|editor, _, _| {
18995        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18996        {
18997            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18998        } else {
18999            panic!("expected completion menu to be open");
19000        }
19001    });
19002}
19003
19004fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19005    let entries = menu.entries.borrow();
19006    entries.iter().map(|mat| mat.string.clone()).collect()
19007}
19008
19009#[gpui::test]
19010async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19011    init_test(cx, |settings| {
19012        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19013    });
19014
19015    let fs = FakeFs::new(cx.executor());
19016    fs.insert_file(path!("/file.ts"), Default::default()).await;
19017
19018    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19019    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19020
19021    language_registry.add(Arc::new(Language::new(
19022        LanguageConfig {
19023            name: "TypeScript".into(),
19024            matcher: LanguageMatcher {
19025                path_suffixes: vec!["ts".to_string()],
19026                ..Default::default()
19027            },
19028            ..Default::default()
19029        },
19030        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19031    )));
19032    update_test_language_settings(cx, |settings| {
19033        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19034    });
19035
19036    let test_plugin = "test_plugin";
19037    let _ = language_registry.register_fake_lsp(
19038        "TypeScript",
19039        FakeLspAdapter {
19040            prettier_plugins: vec![test_plugin],
19041            ..Default::default()
19042        },
19043    );
19044
19045    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19046    let buffer = project
19047        .update(cx, |project, cx| {
19048            project.open_local_buffer(path!("/file.ts"), cx)
19049        })
19050        .await
19051        .unwrap();
19052
19053    let buffer_text = "one\ntwo\nthree\n";
19054    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19055    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19056    editor.update_in(cx, |editor, window, cx| {
19057        editor.set_text(buffer_text, window, cx)
19058    });
19059
19060    editor
19061        .update_in(cx, |editor, window, cx| {
19062            editor.perform_format(
19063                project.clone(),
19064                FormatTrigger::Manual,
19065                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19066                window,
19067                cx,
19068            )
19069        })
19070        .unwrap()
19071        .await;
19072    assert_eq!(
19073        editor.update(cx, |editor, cx| editor.text(cx)),
19074        buffer_text.to_string() + prettier_format_suffix,
19075        "Test prettier formatting was not applied to the original buffer text",
19076    );
19077
19078    update_test_language_settings(cx, |settings| {
19079        settings.defaults.formatter = Some(FormatterList::default())
19080    });
19081    let format = editor.update_in(cx, |editor, window, cx| {
19082        editor.perform_format(
19083            project.clone(),
19084            FormatTrigger::Manual,
19085            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19086            window,
19087            cx,
19088        )
19089    });
19090    format.await.unwrap();
19091    assert_eq!(
19092        editor.update(cx, |editor, cx| editor.text(cx)),
19093        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19094        "Autoformatting (via test prettier) was not applied to the original buffer text",
19095    );
19096}
19097
19098#[gpui::test]
19099async fn test_addition_reverts(cx: &mut TestAppContext) {
19100    init_test(cx, |_| {});
19101    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19102    let base_text = indoc! {r#"
19103        struct Row;
19104        struct Row1;
19105        struct Row2;
19106
19107        struct Row4;
19108        struct Row5;
19109        struct Row6;
19110
19111        struct Row8;
19112        struct Row9;
19113        struct Row10;"#};
19114
19115    // When addition hunks are not adjacent to carets, no hunk revert is performed
19116    assert_hunk_revert(
19117        indoc! {r#"struct Row;
19118                   struct Row1;
19119                   struct Row1.1;
19120                   struct Row1.2;
19121                   struct Row2;ˇ
19122
19123                   struct Row4;
19124                   struct Row5;
19125                   struct Row6;
19126
19127                   struct Row8;
19128                   ˇstruct Row9;
19129                   struct Row9.1;
19130                   struct Row9.2;
19131                   struct Row9.3;
19132                   struct Row10;"#},
19133        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19134        indoc! {r#"struct Row;
19135                   struct Row1;
19136                   struct Row1.1;
19137                   struct Row1.2;
19138                   struct Row2;ˇ
19139
19140                   struct Row4;
19141                   struct Row5;
19142                   struct Row6;
19143
19144                   struct Row8;
19145                   ˇstruct Row9;
19146                   struct Row9.1;
19147                   struct Row9.2;
19148                   struct Row9.3;
19149                   struct Row10;"#},
19150        base_text,
19151        &mut cx,
19152    );
19153    // Same for selections
19154    assert_hunk_revert(
19155        indoc! {r#"struct Row;
19156                   struct Row1;
19157                   struct Row2;
19158                   struct Row2.1;
19159                   struct Row2.2;
19160                   «ˇ
19161                   struct Row4;
19162                   struct» Row5;
19163                   «struct Row6;
19164                   ˇ»
19165                   struct Row9.1;
19166                   struct Row9.2;
19167                   struct Row9.3;
19168                   struct Row8;
19169                   struct Row9;
19170                   struct Row10;"#},
19171        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19172        indoc! {r#"struct Row;
19173                   struct Row1;
19174                   struct Row2;
19175                   struct Row2.1;
19176                   struct Row2.2;
19177                   «ˇ
19178                   struct Row4;
19179                   struct» Row5;
19180                   «struct Row6;
19181                   ˇ»
19182                   struct Row9.1;
19183                   struct Row9.2;
19184                   struct Row9.3;
19185                   struct Row8;
19186                   struct Row9;
19187                   struct Row10;"#},
19188        base_text,
19189        &mut cx,
19190    );
19191
19192    // When carets and selections intersect the addition hunks, those are reverted.
19193    // Adjacent carets got merged.
19194    assert_hunk_revert(
19195        indoc! {r#"struct Row;
19196                   ˇ// something on the top
19197                   struct Row1;
19198                   struct Row2;
19199                   struct Roˇw3.1;
19200                   struct Row2.2;
19201                   struct Row2.3;ˇ
19202
19203                   struct Row4;
19204                   struct ˇRow5.1;
19205                   struct Row5.2;
19206                   struct «Rowˇ»5.3;
19207                   struct Row5;
19208                   struct Row6;
19209                   ˇ
19210                   struct Row9.1;
19211                   struct «Rowˇ»9.2;
19212                   struct «ˇRow»9.3;
19213                   struct Row8;
19214                   struct Row9;
19215                   «ˇ// something on bottom»
19216                   struct Row10;"#},
19217        vec![
19218            DiffHunkStatusKind::Added,
19219            DiffHunkStatusKind::Added,
19220            DiffHunkStatusKind::Added,
19221            DiffHunkStatusKind::Added,
19222            DiffHunkStatusKind::Added,
19223        ],
19224        indoc! {r#"struct Row;
19225                   ˇstruct Row1;
19226                   struct Row2;
19227                   ˇ
19228                   struct Row4;
19229                   ˇstruct Row5;
19230                   struct Row6;
19231                   ˇ
19232                   ˇstruct Row8;
19233                   struct Row9;
19234                   ˇstruct Row10;"#},
19235        base_text,
19236        &mut cx,
19237    );
19238}
19239
19240#[gpui::test]
19241async fn test_modification_reverts(cx: &mut TestAppContext) {
19242    init_test(cx, |_| {});
19243    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19244    let base_text = indoc! {r#"
19245        struct Row;
19246        struct Row1;
19247        struct Row2;
19248
19249        struct Row4;
19250        struct Row5;
19251        struct Row6;
19252
19253        struct Row8;
19254        struct Row9;
19255        struct Row10;"#};
19256
19257    // Modification hunks behave the same as the addition ones.
19258    assert_hunk_revert(
19259        indoc! {r#"struct Row;
19260                   struct Row1;
19261                   struct Row33;
19262                   ˇ
19263                   struct Row4;
19264                   struct Row5;
19265                   struct Row6;
19266                   ˇ
19267                   struct Row99;
19268                   struct Row9;
19269                   struct Row10;"#},
19270        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19271        indoc! {r#"struct Row;
19272                   struct Row1;
19273                   struct Row33;
19274                   ˇ
19275                   struct Row4;
19276                   struct Row5;
19277                   struct Row6;
19278                   ˇ
19279                   struct Row99;
19280                   struct Row9;
19281                   struct Row10;"#},
19282        base_text,
19283        &mut cx,
19284    );
19285    assert_hunk_revert(
19286        indoc! {r#"struct Row;
19287                   struct Row1;
19288                   struct Row33;
19289                   «ˇ
19290                   struct Row4;
19291                   struct» Row5;
19292                   «struct Row6;
19293                   ˇ»
19294                   struct Row99;
19295                   struct Row9;
19296                   struct Row10;"#},
19297        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19298        indoc! {r#"struct Row;
19299                   struct Row1;
19300                   struct Row33;
19301                   «ˇ
19302                   struct Row4;
19303                   struct» Row5;
19304                   «struct Row6;
19305                   ˇ»
19306                   struct Row99;
19307                   struct Row9;
19308                   struct Row10;"#},
19309        base_text,
19310        &mut cx,
19311    );
19312
19313    assert_hunk_revert(
19314        indoc! {r#"ˇstruct Row1.1;
19315                   struct Row1;
19316                   «ˇstr»uct Row22;
19317
19318                   struct ˇRow44;
19319                   struct Row5;
19320                   struct «Rˇ»ow66;ˇ
19321
19322                   «struˇ»ct Row88;
19323                   struct Row9;
19324                   struct Row1011;ˇ"#},
19325        vec![
19326            DiffHunkStatusKind::Modified,
19327            DiffHunkStatusKind::Modified,
19328            DiffHunkStatusKind::Modified,
19329            DiffHunkStatusKind::Modified,
19330            DiffHunkStatusKind::Modified,
19331            DiffHunkStatusKind::Modified,
19332        ],
19333        indoc! {r#"struct Row;
19334                   ˇstruct Row1;
19335                   struct Row2;
19336                   ˇ
19337                   struct Row4;
19338                   ˇstruct Row5;
19339                   struct Row6;
19340                   ˇ
19341                   struct Row8;
19342                   ˇstruct Row9;
19343                   struct Row10;ˇ"#},
19344        base_text,
19345        &mut cx,
19346    );
19347}
19348
19349#[gpui::test]
19350async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19351    init_test(cx, |_| {});
19352    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19353    let base_text = indoc! {r#"
19354        one
19355
19356        two
19357        three
19358        "#};
19359
19360    cx.set_head_text(base_text);
19361    cx.set_state("\nˇ\n");
19362    cx.executor().run_until_parked();
19363    cx.update_editor(|editor, _window, cx| {
19364        editor.expand_selected_diff_hunks(cx);
19365    });
19366    cx.executor().run_until_parked();
19367    cx.update_editor(|editor, window, cx| {
19368        editor.backspace(&Default::default(), window, cx);
19369    });
19370    cx.run_until_parked();
19371    cx.assert_state_with_diff(
19372        indoc! {r#"
19373
19374        - two
19375        - threeˇ
19376        +
19377        "#}
19378        .to_string(),
19379    );
19380}
19381
19382#[gpui::test]
19383async fn test_deletion_reverts(cx: &mut TestAppContext) {
19384    init_test(cx, |_| {});
19385    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19386    let base_text = indoc! {r#"struct Row;
19387struct Row1;
19388struct Row2;
19389
19390struct Row4;
19391struct Row5;
19392struct Row6;
19393
19394struct Row8;
19395struct Row9;
19396struct Row10;"#};
19397
19398    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19399    assert_hunk_revert(
19400        indoc! {r#"struct Row;
19401                   struct Row2;
19402
19403                   ˇstruct Row4;
19404                   struct Row5;
19405                   struct Row6;
19406                   ˇ
19407                   struct Row8;
19408                   struct Row10;"#},
19409        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19410        indoc! {r#"struct Row;
19411                   struct Row2;
19412
19413                   ˇstruct Row4;
19414                   struct Row5;
19415                   struct Row6;
19416                   ˇ
19417                   struct Row8;
19418                   struct Row10;"#},
19419        base_text,
19420        &mut cx,
19421    );
19422    assert_hunk_revert(
19423        indoc! {r#"struct Row;
19424                   struct Row2;
19425
19426                   «ˇstruct Row4;
19427                   struct» Row5;
19428                   «struct Row6;
19429                   ˇ»
19430                   struct Row8;
19431                   struct Row10;"#},
19432        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19433        indoc! {r#"struct Row;
19434                   struct Row2;
19435
19436                   «ˇstruct Row4;
19437                   struct» Row5;
19438                   «struct Row6;
19439                   ˇ»
19440                   struct Row8;
19441                   struct Row10;"#},
19442        base_text,
19443        &mut cx,
19444    );
19445
19446    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19447    assert_hunk_revert(
19448        indoc! {r#"struct Row;
19449                   ˇstruct Row2;
19450
19451                   struct Row4;
19452                   struct Row5;
19453                   struct Row6;
19454
19455                   struct Row8;ˇ
19456                   struct Row10;"#},
19457        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19458        indoc! {r#"struct Row;
19459                   struct Row1;
19460                   ˇstruct Row2;
19461
19462                   struct Row4;
19463                   struct Row5;
19464                   struct Row6;
19465
19466                   struct Row8;ˇ
19467                   struct Row9;
19468                   struct Row10;"#},
19469        base_text,
19470        &mut cx,
19471    );
19472    assert_hunk_revert(
19473        indoc! {r#"struct Row;
19474                   struct Row2«ˇ;
19475                   struct Row4;
19476                   struct» Row5;
19477                   «struct Row6;
19478
19479                   struct Row8;ˇ»
19480                   struct Row10;"#},
19481        vec![
19482            DiffHunkStatusKind::Deleted,
19483            DiffHunkStatusKind::Deleted,
19484            DiffHunkStatusKind::Deleted,
19485        ],
19486        indoc! {r#"struct Row;
19487                   struct Row1;
19488                   struct Row2«ˇ;
19489
19490                   struct Row4;
19491                   struct» Row5;
19492                   «struct Row6;
19493
19494                   struct Row8;ˇ»
19495                   struct Row9;
19496                   struct Row10;"#},
19497        base_text,
19498        &mut cx,
19499    );
19500}
19501
19502#[gpui::test]
19503async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19504    init_test(cx, |_| {});
19505
19506    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19507    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19508    let base_text_3 =
19509        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19510
19511    let text_1 = edit_first_char_of_every_line(base_text_1);
19512    let text_2 = edit_first_char_of_every_line(base_text_2);
19513    let text_3 = edit_first_char_of_every_line(base_text_3);
19514
19515    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19516    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19517    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19518
19519    let multibuffer = cx.new(|cx| {
19520        let mut multibuffer = MultiBuffer::new(ReadWrite);
19521        multibuffer.push_excerpts(
19522            buffer_1.clone(),
19523            [
19524                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19525                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19526                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19527            ],
19528            cx,
19529        );
19530        multibuffer.push_excerpts(
19531            buffer_2.clone(),
19532            [
19533                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19534                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19535                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19536            ],
19537            cx,
19538        );
19539        multibuffer.push_excerpts(
19540            buffer_3.clone(),
19541            [
19542                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19543                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19544                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19545            ],
19546            cx,
19547        );
19548        multibuffer
19549    });
19550
19551    let fs = FakeFs::new(cx.executor());
19552    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19553    let (editor, cx) = cx
19554        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19555    editor.update_in(cx, |editor, _window, cx| {
19556        for (buffer, diff_base) in [
19557            (buffer_1.clone(), base_text_1),
19558            (buffer_2.clone(), base_text_2),
19559            (buffer_3.clone(), base_text_3),
19560        ] {
19561            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19562            editor
19563                .buffer
19564                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19565        }
19566    });
19567    cx.executor().run_until_parked();
19568
19569    editor.update_in(cx, |editor, window, cx| {
19570        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}");
19571        editor.select_all(&SelectAll, window, cx);
19572        editor.git_restore(&Default::default(), window, cx);
19573    });
19574    cx.executor().run_until_parked();
19575
19576    // When all ranges are selected, all buffer hunks are reverted.
19577    editor.update(cx, |editor, cx| {
19578        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");
19579    });
19580    buffer_1.update(cx, |buffer, _| {
19581        assert_eq!(buffer.text(), base_text_1);
19582    });
19583    buffer_2.update(cx, |buffer, _| {
19584        assert_eq!(buffer.text(), base_text_2);
19585    });
19586    buffer_3.update(cx, |buffer, _| {
19587        assert_eq!(buffer.text(), base_text_3);
19588    });
19589
19590    editor.update_in(cx, |editor, window, cx| {
19591        editor.undo(&Default::default(), window, cx);
19592    });
19593
19594    editor.update_in(cx, |editor, window, cx| {
19595        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19596            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19597        });
19598        editor.git_restore(&Default::default(), window, cx);
19599    });
19600
19601    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19602    // but not affect buffer_2 and its related excerpts.
19603    editor.update(cx, |editor, cx| {
19604        assert_eq!(
19605            editor.text(cx),
19606            "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}"
19607        );
19608    });
19609    buffer_1.update(cx, |buffer, _| {
19610        assert_eq!(buffer.text(), base_text_1);
19611    });
19612    buffer_2.update(cx, |buffer, _| {
19613        assert_eq!(
19614            buffer.text(),
19615            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19616        );
19617    });
19618    buffer_3.update(cx, |buffer, _| {
19619        assert_eq!(
19620            buffer.text(),
19621            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19622        );
19623    });
19624
19625    fn edit_first_char_of_every_line(text: &str) -> String {
19626        text.split('\n')
19627            .map(|line| format!("X{}", &line[1..]))
19628            .collect::<Vec<_>>()
19629            .join("\n")
19630    }
19631}
19632
19633#[gpui::test]
19634async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19635    init_test(cx, |_| {});
19636
19637    let cols = 4;
19638    let rows = 10;
19639    let sample_text_1 = sample_text(rows, cols, 'a');
19640    assert_eq!(
19641        sample_text_1,
19642        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19643    );
19644    let sample_text_2 = sample_text(rows, cols, 'l');
19645    assert_eq!(
19646        sample_text_2,
19647        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19648    );
19649    let sample_text_3 = sample_text(rows, cols, 'v');
19650    assert_eq!(
19651        sample_text_3,
19652        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19653    );
19654
19655    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19656    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19657    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19658
19659    let multi_buffer = cx.new(|cx| {
19660        let mut multibuffer = MultiBuffer::new(ReadWrite);
19661        multibuffer.push_excerpts(
19662            buffer_1.clone(),
19663            [
19664                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19665                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19666                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19667            ],
19668            cx,
19669        );
19670        multibuffer.push_excerpts(
19671            buffer_2.clone(),
19672            [
19673                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19674                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19675                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19676            ],
19677            cx,
19678        );
19679        multibuffer.push_excerpts(
19680            buffer_3.clone(),
19681            [
19682                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19683                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19684                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19685            ],
19686            cx,
19687        );
19688        multibuffer
19689    });
19690
19691    let fs = FakeFs::new(cx.executor());
19692    fs.insert_tree(
19693        "/a",
19694        json!({
19695            "main.rs": sample_text_1,
19696            "other.rs": sample_text_2,
19697            "lib.rs": sample_text_3,
19698        }),
19699    )
19700    .await;
19701    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19702    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19703    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19704    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19705        Editor::new(
19706            EditorMode::full(),
19707            multi_buffer,
19708            Some(project.clone()),
19709            window,
19710            cx,
19711        )
19712    });
19713    let multibuffer_item_id = workspace
19714        .update(cx, |workspace, window, cx| {
19715            assert!(
19716                workspace.active_item(cx).is_none(),
19717                "active item should be None before the first item is added"
19718            );
19719            workspace.add_item_to_active_pane(
19720                Box::new(multi_buffer_editor.clone()),
19721                None,
19722                true,
19723                window,
19724                cx,
19725            );
19726            let active_item = workspace
19727                .active_item(cx)
19728                .expect("should have an active item after adding the multi buffer");
19729            assert_eq!(
19730                active_item.buffer_kind(cx),
19731                ItemBufferKind::Multibuffer,
19732                "A multi buffer was expected to active after adding"
19733            );
19734            active_item.item_id()
19735        })
19736        .unwrap();
19737    cx.executor().run_until_parked();
19738
19739    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19740        editor.change_selections(
19741            SelectionEffects::scroll(Autoscroll::Next),
19742            window,
19743            cx,
19744            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
19745        );
19746        editor.open_excerpts(&OpenExcerpts, window, cx);
19747    });
19748    cx.executor().run_until_parked();
19749    let first_item_id = workspace
19750        .update(cx, |workspace, window, cx| {
19751            let active_item = workspace
19752                .active_item(cx)
19753                .expect("should have an active item after navigating into the 1st buffer");
19754            let first_item_id = active_item.item_id();
19755            assert_ne!(
19756                first_item_id, multibuffer_item_id,
19757                "Should navigate into the 1st buffer and activate it"
19758            );
19759            assert_eq!(
19760                active_item.buffer_kind(cx),
19761                ItemBufferKind::Singleton,
19762                "New active item should be a singleton buffer"
19763            );
19764            assert_eq!(
19765                active_item
19766                    .act_as::<Editor>(cx)
19767                    .expect("should have navigated into an editor for the 1st buffer")
19768                    .read(cx)
19769                    .text(cx),
19770                sample_text_1
19771            );
19772
19773            workspace
19774                .go_back(workspace.active_pane().downgrade(), window, cx)
19775                .detach_and_log_err(cx);
19776
19777            first_item_id
19778        })
19779        .unwrap();
19780    cx.executor().run_until_parked();
19781    workspace
19782        .update(cx, |workspace, _, cx| {
19783            let active_item = workspace
19784                .active_item(cx)
19785                .expect("should have an active item after navigating back");
19786            assert_eq!(
19787                active_item.item_id(),
19788                multibuffer_item_id,
19789                "Should navigate back to the multi buffer"
19790            );
19791            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19792        })
19793        .unwrap();
19794
19795    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19796        editor.change_selections(
19797            SelectionEffects::scroll(Autoscroll::Next),
19798            window,
19799            cx,
19800            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
19801        );
19802        editor.open_excerpts(&OpenExcerpts, window, cx);
19803    });
19804    cx.executor().run_until_parked();
19805    let second_item_id = workspace
19806        .update(cx, |workspace, window, cx| {
19807            let active_item = workspace
19808                .active_item(cx)
19809                .expect("should have an active item after navigating into the 2nd buffer");
19810            let second_item_id = active_item.item_id();
19811            assert_ne!(
19812                second_item_id, multibuffer_item_id,
19813                "Should navigate away from the multibuffer"
19814            );
19815            assert_ne!(
19816                second_item_id, first_item_id,
19817                "Should navigate into the 2nd buffer and activate it"
19818            );
19819            assert_eq!(
19820                active_item.buffer_kind(cx),
19821                ItemBufferKind::Singleton,
19822                "New active item should be a singleton buffer"
19823            );
19824            assert_eq!(
19825                active_item
19826                    .act_as::<Editor>(cx)
19827                    .expect("should have navigated into an editor")
19828                    .read(cx)
19829                    .text(cx),
19830                sample_text_2
19831            );
19832
19833            workspace
19834                .go_back(workspace.active_pane().downgrade(), window, cx)
19835                .detach_and_log_err(cx);
19836
19837            second_item_id
19838        })
19839        .unwrap();
19840    cx.executor().run_until_parked();
19841    workspace
19842        .update(cx, |workspace, _, cx| {
19843            let active_item = workspace
19844                .active_item(cx)
19845                .expect("should have an active item after navigating back from the 2nd buffer");
19846            assert_eq!(
19847                active_item.item_id(),
19848                multibuffer_item_id,
19849                "Should navigate back from the 2nd buffer to the multi buffer"
19850            );
19851            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19852        })
19853        .unwrap();
19854
19855    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19856        editor.change_selections(
19857            SelectionEffects::scroll(Autoscroll::Next),
19858            window,
19859            cx,
19860            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
19861        );
19862        editor.open_excerpts(&OpenExcerpts, window, cx);
19863    });
19864    cx.executor().run_until_parked();
19865    workspace
19866        .update(cx, |workspace, window, cx| {
19867            let active_item = workspace
19868                .active_item(cx)
19869                .expect("should have an active item after navigating into the 3rd buffer");
19870            let third_item_id = active_item.item_id();
19871            assert_ne!(
19872                third_item_id, multibuffer_item_id,
19873                "Should navigate into the 3rd buffer and activate it"
19874            );
19875            assert_ne!(third_item_id, first_item_id);
19876            assert_ne!(third_item_id, second_item_id);
19877            assert_eq!(
19878                active_item.buffer_kind(cx),
19879                ItemBufferKind::Singleton,
19880                "New active item should be a singleton buffer"
19881            );
19882            assert_eq!(
19883                active_item
19884                    .act_as::<Editor>(cx)
19885                    .expect("should have navigated into an editor")
19886                    .read(cx)
19887                    .text(cx),
19888                sample_text_3
19889            );
19890
19891            workspace
19892                .go_back(workspace.active_pane().downgrade(), window, cx)
19893                .detach_and_log_err(cx);
19894        })
19895        .unwrap();
19896    cx.executor().run_until_parked();
19897    workspace
19898        .update(cx, |workspace, _, cx| {
19899            let active_item = workspace
19900                .active_item(cx)
19901                .expect("should have an active item after navigating back from the 3rd buffer");
19902            assert_eq!(
19903                active_item.item_id(),
19904                multibuffer_item_id,
19905                "Should navigate back from the 3rd buffer to the multi buffer"
19906            );
19907            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19908        })
19909        .unwrap();
19910}
19911
19912#[gpui::test]
19913async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19914    init_test(cx, |_| {});
19915
19916    let mut cx = EditorTestContext::new(cx).await;
19917
19918    let diff_base = r#"
19919        use some::mod;
19920
19921        const A: u32 = 42;
19922
19923        fn main() {
19924            println!("hello");
19925
19926            println!("world");
19927        }
19928        "#
19929    .unindent();
19930
19931    cx.set_state(
19932        &r#"
19933        use some::modified;
19934
19935        ˇ
19936        fn main() {
19937            println!("hello there");
19938
19939            println!("around the");
19940            println!("world");
19941        }
19942        "#
19943        .unindent(),
19944    );
19945
19946    cx.set_head_text(&diff_base);
19947    executor.run_until_parked();
19948
19949    cx.update_editor(|editor, window, cx| {
19950        editor.go_to_next_hunk(&GoToHunk, window, cx);
19951        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19952    });
19953    executor.run_until_parked();
19954    cx.assert_state_with_diff(
19955        r#"
19956          use some::modified;
19957
19958
19959          fn main() {
19960        -     println!("hello");
19961        + ˇ    println!("hello there");
19962
19963              println!("around the");
19964              println!("world");
19965          }
19966        "#
19967        .unindent(),
19968    );
19969
19970    cx.update_editor(|editor, window, cx| {
19971        for _ in 0..2 {
19972            editor.go_to_next_hunk(&GoToHunk, window, cx);
19973            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19974        }
19975    });
19976    executor.run_until_parked();
19977    cx.assert_state_with_diff(
19978        r#"
19979        - use some::mod;
19980        + ˇuse some::modified;
19981
19982
19983          fn main() {
19984        -     println!("hello");
19985        +     println!("hello there");
19986
19987        +     println!("around the");
19988              println!("world");
19989          }
19990        "#
19991        .unindent(),
19992    );
19993
19994    cx.update_editor(|editor, window, cx| {
19995        editor.go_to_next_hunk(&GoToHunk, window, cx);
19996        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19997    });
19998    executor.run_until_parked();
19999    cx.assert_state_with_diff(
20000        r#"
20001        - use some::mod;
20002        + use some::modified;
20003
20004        - const A: u32 = 42;
20005          ˇ
20006          fn main() {
20007        -     println!("hello");
20008        +     println!("hello there");
20009
20010        +     println!("around the");
20011              println!("world");
20012          }
20013        "#
20014        .unindent(),
20015    );
20016
20017    cx.update_editor(|editor, window, cx| {
20018        editor.cancel(&Cancel, window, cx);
20019    });
20020
20021    cx.assert_state_with_diff(
20022        r#"
20023          use some::modified;
20024
20025          ˇ
20026          fn main() {
20027              println!("hello there");
20028
20029              println!("around the");
20030              println!("world");
20031          }
20032        "#
20033        .unindent(),
20034    );
20035}
20036
20037#[gpui::test]
20038async fn test_diff_base_change_with_expanded_diff_hunks(
20039    executor: BackgroundExecutor,
20040    cx: &mut TestAppContext,
20041) {
20042    init_test(cx, |_| {});
20043
20044    let mut cx = EditorTestContext::new(cx).await;
20045
20046    let diff_base = r#"
20047        use some::mod1;
20048        use some::mod2;
20049
20050        const A: u32 = 42;
20051        const B: u32 = 42;
20052        const C: u32 = 42;
20053
20054        fn main() {
20055            println!("hello");
20056
20057            println!("world");
20058        }
20059        "#
20060    .unindent();
20061
20062    cx.set_state(
20063        &r#"
20064        use some::mod2;
20065
20066        const A: u32 = 42;
20067        const C: u32 = 42;
20068
20069        fn main(ˇ) {
20070            //println!("hello");
20071
20072            println!("world");
20073            //
20074            //
20075        }
20076        "#
20077        .unindent(),
20078    );
20079
20080    cx.set_head_text(&diff_base);
20081    executor.run_until_parked();
20082
20083    cx.update_editor(|editor, window, cx| {
20084        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20085    });
20086    executor.run_until_parked();
20087    cx.assert_state_with_diff(
20088        r#"
20089        - use some::mod1;
20090          use some::mod2;
20091
20092          const A: u32 = 42;
20093        - const B: u32 = 42;
20094          const C: u32 = 42;
20095
20096          fn main(ˇ) {
20097        -     println!("hello");
20098        +     //println!("hello");
20099
20100              println!("world");
20101        +     //
20102        +     //
20103          }
20104        "#
20105        .unindent(),
20106    );
20107
20108    cx.set_head_text("new diff base!");
20109    executor.run_until_parked();
20110    cx.assert_state_with_diff(
20111        r#"
20112        - new diff base!
20113        + use some::mod2;
20114        +
20115        + const A: u32 = 42;
20116        + const C: u32 = 42;
20117        +
20118        + fn main(ˇ) {
20119        +     //println!("hello");
20120        +
20121        +     println!("world");
20122        +     //
20123        +     //
20124        + }
20125        "#
20126        .unindent(),
20127    );
20128}
20129
20130#[gpui::test]
20131async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20132    init_test(cx, |_| {});
20133
20134    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20135    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20136    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20137    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20138    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20139    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20140
20141    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20142    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20143    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20144
20145    let multi_buffer = cx.new(|cx| {
20146        let mut multibuffer = MultiBuffer::new(ReadWrite);
20147        multibuffer.push_excerpts(
20148            buffer_1.clone(),
20149            [
20150                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20151                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20152                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20153            ],
20154            cx,
20155        );
20156        multibuffer.push_excerpts(
20157            buffer_2.clone(),
20158            [
20159                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20160                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20161                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20162            ],
20163            cx,
20164        );
20165        multibuffer.push_excerpts(
20166            buffer_3.clone(),
20167            [
20168                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20169                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20170                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20171            ],
20172            cx,
20173        );
20174        multibuffer
20175    });
20176
20177    let editor =
20178        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20179    editor
20180        .update(cx, |editor, _window, cx| {
20181            for (buffer, diff_base) in [
20182                (buffer_1.clone(), file_1_old),
20183                (buffer_2.clone(), file_2_old),
20184                (buffer_3.clone(), file_3_old),
20185            ] {
20186                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20187                editor
20188                    .buffer
20189                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20190            }
20191        })
20192        .unwrap();
20193
20194    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20195    cx.run_until_parked();
20196
20197    cx.assert_editor_state(
20198        &"
20199            ˇaaa
20200            ccc
20201            ddd
20202
20203            ggg
20204            hhh
20205
20206
20207            lll
20208            mmm
20209            NNN
20210
20211            qqq
20212            rrr
20213
20214            uuu
20215            111
20216            222
20217            333
20218
20219            666
20220            777
20221
20222            000
20223            !!!"
20224        .unindent(),
20225    );
20226
20227    cx.update_editor(|editor, window, cx| {
20228        editor.select_all(&SelectAll, window, cx);
20229        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20230    });
20231    cx.executor().run_until_parked();
20232
20233    cx.assert_state_with_diff(
20234        "
20235            «aaa
20236          - bbb
20237            ccc
20238            ddd
20239
20240            ggg
20241            hhh
20242
20243
20244            lll
20245            mmm
20246          - nnn
20247          + NNN
20248
20249            qqq
20250            rrr
20251
20252            uuu
20253            111
20254            222
20255            333
20256
20257          + 666
20258            777
20259
20260            000
20261            !!!ˇ»"
20262            .unindent(),
20263    );
20264}
20265
20266#[gpui::test]
20267async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20268    init_test(cx, |_| {});
20269
20270    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20271    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20272
20273    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20274    let multi_buffer = cx.new(|cx| {
20275        let mut multibuffer = MultiBuffer::new(ReadWrite);
20276        multibuffer.push_excerpts(
20277            buffer.clone(),
20278            [
20279                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20280                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20281                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20282            ],
20283            cx,
20284        );
20285        multibuffer
20286    });
20287
20288    let editor =
20289        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20290    editor
20291        .update(cx, |editor, _window, cx| {
20292            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20293            editor
20294                .buffer
20295                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20296        })
20297        .unwrap();
20298
20299    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20300    cx.run_until_parked();
20301
20302    cx.update_editor(|editor, window, cx| {
20303        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20304    });
20305    cx.executor().run_until_parked();
20306
20307    // When the start of a hunk coincides with the start of its excerpt,
20308    // the hunk is expanded. When the start of a hunk is earlier than
20309    // the start of its excerpt, the hunk is not expanded.
20310    cx.assert_state_with_diff(
20311        "
20312            ˇaaa
20313          - bbb
20314          + BBB
20315
20316          - ddd
20317          - eee
20318          + DDD
20319          + EEE
20320            fff
20321
20322            iii
20323        "
20324        .unindent(),
20325    );
20326}
20327
20328#[gpui::test]
20329async fn test_edits_around_expanded_insertion_hunks(
20330    executor: BackgroundExecutor,
20331    cx: &mut TestAppContext,
20332) {
20333    init_test(cx, |_| {});
20334
20335    let mut cx = EditorTestContext::new(cx).await;
20336
20337    let diff_base = r#"
20338        use some::mod1;
20339        use some::mod2;
20340
20341        const A: u32 = 42;
20342
20343        fn main() {
20344            println!("hello");
20345
20346            println!("world");
20347        }
20348        "#
20349    .unindent();
20350    executor.run_until_parked();
20351    cx.set_state(
20352        &r#"
20353        use some::mod1;
20354        use some::mod2;
20355
20356        const A: u32 = 42;
20357        const B: u32 = 42;
20358        const C: u32 = 42;
20359        ˇ
20360
20361        fn main() {
20362            println!("hello");
20363
20364            println!("world");
20365        }
20366        "#
20367        .unindent(),
20368    );
20369
20370    cx.set_head_text(&diff_base);
20371    executor.run_until_parked();
20372
20373    cx.update_editor(|editor, window, cx| {
20374        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20375    });
20376    executor.run_until_parked();
20377
20378    cx.assert_state_with_diff(
20379        r#"
20380        use some::mod1;
20381        use some::mod2;
20382
20383        const A: u32 = 42;
20384      + const B: u32 = 42;
20385      + const C: u32 = 42;
20386      + ˇ
20387
20388        fn main() {
20389            println!("hello");
20390
20391            println!("world");
20392        }
20393      "#
20394        .unindent(),
20395    );
20396
20397    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20398    executor.run_until_parked();
20399
20400    cx.assert_state_with_diff(
20401        r#"
20402        use some::mod1;
20403        use some::mod2;
20404
20405        const A: u32 = 42;
20406      + const B: u32 = 42;
20407      + const C: u32 = 42;
20408      + const D: u32 = 42;
20409      + ˇ
20410
20411        fn main() {
20412            println!("hello");
20413
20414            println!("world");
20415        }
20416      "#
20417        .unindent(),
20418    );
20419
20420    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20421    executor.run_until_parked();
20422
20423    cx.assert_state_with_diff(
20424        r#"
20425        use some::mod1;
20426        use some::mod2;
20427
20428        const A: u32 = 42;
20429      + const B: u32 = 42;
20430      + const C: u32 = 42;
20431      + const D: u32 = 42;
20432      + const E: u32 = 42;
20433      + ˇ
20434
20435        fn main() {
20436            println!("hello");
20437
20438            println!("world");
20439        }
20440      "#
20441        .unindent(),
20442    );
20443
20444    cx.update_editor(|editor, window, cx| {
20445        editor.delete_line(&DeleteLine, window, cx);
20446    });
20447    executor.run_until_parked();
20448
20449    cx.assert_state_with_diff(
20450        r#"
20451        use some::mod1;
20452        use some::mod2;
20453
20454        const A: u32 = 42;
20455      + const B: u32 = 42;
20456      + const C: u32 = 42;
20457      + const D: u32 = 42;
20458      + const E: u32 = 42;
20459        ˇ
20460        fn main() {
20461            println!("hello");
20462
20463            println!("world");
20464        }
20465      "#
20466        .unindent(),
20467    );
20468
20469    cx.update_editor(|editor, window, cx| {
20470        editor.move_up(&MoveUp, window, cx);
20471        editor.delete_line(&DeleteLine, window, cx);
20472        editor.move_up(&MoveUp, window, cx);
20473        editor.delete_line(&DeleteLine, window, cx);
20474        editor.move_up(&MoveUp, window, cx);
20475        editor.delete_line(&DeleteLine, window, cx);
20476    });
20477    executor.run_until_parked();
20478    cx.assert_state_with_diff(
20479        r#"
20480        use some::mod1;
20481        use some::mod2;
20482
20483        const A: u32 = 42;
20484      + const B: u32 = 42;
20485        ˇ
20486        fn main() {
20487            println!("hello");
20488
20489            println!("world");
20490        }
20491      "#
20492        .unindent(),
20493    );
20494
20495    cx.update_editor(|editor, window, cx| {
20496        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20497        editor.delete_line(&DeleteLine, window, cx);
20498    });
20499    executor.run_until_parked();
20500    cx.assert_state_with_diff(
20501        r#"
20502        ˇ
20503        fn main() {
20504            println!("hello");
20505
20506            println!("world");
20507        }
20508      "#
20509        .unindent(),
20510    );
20511}
20512
20513#[gpui::test]
20514async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20515    init_test(cx, |_| {});
20516
20517    let mut cx = EditorTestContext::new(cx).await;
20518    cx.set_head_text(indoc! { "
20519        one
20520        two
20521        three
20522        four
20523        five
20524        "
20525    });
20526    cx.set_state(indoc! { "
20527        one
20528        ˇthree
20529        five
20530    "});
20531    cx.run_until_parked();
20532    cx.update_editor(|editor, window, cx| {
20533        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20534    });
20535    cx.assert_state_with_diff(
20536        indoc! { "
20537        one
20538      - two
20539        ˇthree
20540      - four
20541        five
20542    "}
20543        .to_string(),
20544    );
20545    cx.update_editor(|editor, window, cx| {
20546        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20547    });
20548
20549    cx.assert_state_with_diff(
20550        indoc! { "
20551        one
20552        ˇthree
20553        five
20554    "}
20555        .to_string(),
20556    );
20557
20558    cx.set_state(indoc! { "
20559        one
20560        ˇTWO
20561        three
20562        four
20563        five
20564    "});
20565    cx.run_until_parked();
20566    cx.update_editor(|editor, window, cx| {
20567        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20568    });
20569
20570    cx.assert_state_with_diff(
20571        indoc! { "
20572            one
20573          - two
20574          + ˇTWO
20575            three
20576            four
20577            five
20578        "}
20579        .to_string(),
20580    );
20581    cx.update_editor(|editor, window, cx| {
20582        editor.move_up(&Default::default(), window, cx);
20583        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20584    });
20585    cx.assert_state_with_diff(
20586        indoc! { "
20587            one
20588            ˇTWO
20589            three
20590            four
20591            five
20592        "}
20593        .to_string(),
20594    );
20595}
20596
20597#[gpui::test]
20598async fn test_edits_around_expanded_deletion_hunks(
20599    executor: BackgroundExecutor,
20600    cx: &mut TestAppContext,
20601) {
20602    init_test(cx, |_| {});
20603
20604    let mut cx = EditorTestContext::new(cx).await;
20605
20606    let diff_base = r#"
20607        use some::mod1;
20608        use some::mod2;
20609
20610        const A: u32 = 42;
20611        const B: u32 = 42;
20612        const C: u32 = 42;
20613
20614
20615        fn main() {
20616            println!("hello");
20617
20618            println!("world");
20619        }
20620    "#
20621    .unindent();
20622    executor.run_until_parked();
20623    cx.set_state(
20624        &r#"
20625        use some::mod1;
20626        use some::mod2;
20627
20628        ˇconst B: u32 = 42;
20629        const C: u32 = 42;
20630
20631
20632        fn main() {
20633            println!("hello");
20634
20635            println!("world");
20636        }
20637        "#
20638        .unindent(),
20639    );
20640
20641    cx.set_head_text(&diff_base);
20642    executor.run_until_parked();
20643
20644    cx.update_editor(|editor, window, cx| {
20645        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20646    });
20647    executor.run_until_parked();
20648
20649    cx.assert_state_with_diff(
20650        r#"
20651        use some::mod1;
20652        use some::mod2;
20653
20654      - const A: u32 = 42;
20655        ˇconst B: u32 = 42;
20656        const C: u32 = 42;
20657
20658
20659        fn main() {
20660            println!("hello");
20661
20662            println!("world");
20663        }
20664      "#
20665        .unindent(),
20666    );
20667
20668    cx.update_editor(|editor, window, cx| {
20669        editor.delete_line(&DeleteLine, window, cx);
20670    });
20671    executor.run_until_parked();
20672    cx.assert_state_with_diff(
20673        r#"
20674        use some::mod1;
20675        use some::mod2;
20676
20677      - const A: u32 = 42;
20678      - const B: u32 = 42;
20679        ˇconst C: u32 = 42;
20680
20681
20682        fn main() {
20683            println!("hello");
20684
20685            println!("world");
20686        }
20687      "#
20688        .unindent(),
20689    );
20690
20691    cx.update_editor(|editor, window, cx| {
20692        editor.delete_line(&DeleteLine, window, cx);
20693    });
20694    executor.run_until_parked();
20695    cx.assert_state_with_diff(
20696        r#"
20697        use some::mod1;
20698        use some::mod2;
20699
20700      - const A: u32 = 42;
20701      - const B: u32 = 42;
20702      - const C: u32 = 42;
20703        ˇ
20704
20705        fn main() {
20706            println!("hello");
20707
20708            println!("world");
20709        }
20710      "#
20711        .unindent(),
20712    );
20713
20714    cx.update_editor(|editor, window, cx| {
20715        editor.handle_input("replacement", window, cx);
20716    });
20717    executor.run_until_parked();
20718    cx.assert_state_with_diff(
20719        r#"
20720        use some::mod1;
20721        use some::mod2;
20722
20723      - const A: u32 = 42;
20724      - const B: u32 = 42;
20725      - const C: u32 = 42;
20726      -
20727      + replacementˇ
20728
20729        fn main() {
20730            println!("hello");
20731
20732            println!("world");
20733        }
20734      "#
20735        .unindent(),
20736    );
20737}
20738
20739#[gpui::test]
20740async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20741    init_test(cx, |_| {});
20742
20743    let mut cx = EditorTestContext::new(cx).await;
20744
20745    let base_text = r#"
20746        one
20747        two
20748        three
20749        four
20750        five
20751    "#
20752    .unindent();
20753    executor.run_until_parked();
20754    cx.set_state(
20755        &r#"
20756        one
20757        two
20758        fˇour
20759        five
20760        "#
20761        .unindent(),
20762    );
20763
20764    cx.set_head_text(&base_text);
20765    executor.run_until_parked();
20766
20767    cx.update_editor(|editor, window, cx| {
20768        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20769    });
20770    executor.run_until_parked();
20771
20772    cx.assert_state_with_diff(
20773        r#"
20774          one
20775          two
20776        - three
20777          fˇour
20778          five
20779        "#
20780        .unindent(),
20781    );
20782
20783    cx.update_editor(|editor, window, cx| {
20784        editor.backspace(&Backspace, window, cx);
20785        editor.backspace(&Backspace, window, cx);
20786    });
20787    executor.run_until_parked();
20788    cx.assert_state_with_diff(
20789        r#"
20790          one
20791          two
20792        - threeˇ
20793        - four
20794        + our
20795          five
20796        "#
20797        .unindent(),
20798    );
20799}
20800
20801#[gpui::test]
20802async fn test_edit_after_expanded_modification_hunk(
20803    executor: BackgroundExecutor,
20804    cx: &mut TestAppContext,
20805) {
20806    init_test(cx, |_| {});
20807
20808    let mut cx = EditorTestContext::new(cx).await;
20809
20810    let diff_base = r#"
20811        use some::mod1;
20812        use some::mod2;
20813
20814        const A: u32 = 42;
20815        const B: u32 = 42;
20816        const C: u32 = 42;
20817        const D: u32 = 42;
20818
20819
20820        fn main() {
20821            println!("hello");
20822
20823            println!("world");
20824        }"#
20825    .unindent();
20826
20827    cx.set_state(
20828        &r#"
20829        use some::mod1;
20830        use some::mod2;
20831
20832        const A: u32 = 42;
20833        const B: u32 = 42;
20834        const C: u32 = 43ˇ
20835        const D: u32 = 42;
20836
20837
20838        fn main() {
20839            println!("hello");
20840
20841            println!("world");
20842        }"#
20843        .unindent(),
20844    );
20845
20846    cx.set_head_text(&diff_base);
20847    executor.run_until_parked();
20848    cx.update_editor(|editor, window, cx| {
20849        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20850    });
20851    executor.run_until_parked();
20852
20853    cx.assert_state_with_diff(
20854        r#"
20855        use some::mod1;
20856        use some::mod2;
20857
20858        const A: u32 = 42;
20859        const B: u32 = 42;
20860      - const C: u32 = 42;
20861      + const C: u32 = 43ˇ
20862        const D: u32 = 42;
20863
20864
20865        fn main() {
20866            println!("hello");
20867
20868            println!("world");
20869        }"#
20870        .unindent(),
20871    );
20872
20873    cx.update_editor(|editor, window, cx| {
20874        editor.handle_input("\nnew_line\n", window, cx);
20875    });
20876    executor.run_until_parked();
20877
20878    cx.assert_state_with_diff(
20879        r#"
20880        use some::mod1;
20881        use some::mod2;
20882
20883        const A: u32 = 42;
20884        const B: u32 = 42;
20885      - const C: u32 = 42;
20886      + const C: u32 = 43
20887      + new_line
20888      + ˇ
20889        const D: u32 = 42;
20890
20891
20892        fn main() {
20893            println!("hello");
20894
20895            println!("world");
20896        }"#
20897        .unindent(),
20898    );
20899}
20900
20901#[gpui::test]
20902async fn test_stage_and_unstage_added_file_hunk(
20903    executor: BackgroundExecutor,
20904    cx: &mut TestAppContext,
20905) {
20906    init_test(cx, |_| {});
20907
20908    let mut cx = EditorTestContext::new(cx).await;
20909    cx.update_editor(|editor, _, cx| {
20910        editor.set_expand_all_diff_hunks(cx);
20911    });
20912
20913    let working_copy = r#"
20914            ˇfn main() {
20915                println!("hello, world!");
20916            }
20917        "#
20918    .unindent();
20919
20920    cx.set_state(&working_copy);
20921    executor.run_until_parked();
20922
20923    cx.assert_state_with_diff(
20924        r#"
20925            + ˇfn main() {
20926            +     println!("hello, world!");
20927            + }
20928        "#
20929        .unindent(),
20930    );
20931    cx.assert_index_text(None);
20932
20933    cx.update_editor(|editor, window, cx| {
20934        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20935    });
20936    executor.run_until_parked();
20937    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20938    cx.assert_state_with_diff(
20939        r#"
20940            + ˇfn main() {
20941            +     println!("hello, world!");
20942            + }
20943        "#
20944        .unindent(),
20945    );
20946
20947    cx.update_editor(|editor, window, cx| {
20948        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20949    });
20950    executor.run_until_parked();
20951    cx.assert_index_text(None);
20952}
20953
20954async fn setup_indent_guides_editor(
20955    text: &str,
20956    cx: &mut TestAppContext,
20957) -> (BufferId, EditorTestContext) {
20958    init_test(cx, |_| {});
20959
20960    let mut cx = EditorTestContext::new(cx).await;
20961
20962    let buffer_id = cx.update_editor(|editor, window, cx| {
20963        editor.set_text(text, window, cx);
20964        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20965
20966        buffer_ids[0]
20967    });
20968
20969    (buffer_id, cx)
20970}
20971
20972fn assert_indent_guides(
20973    range: Range<u32>,
20974    expected: Vec<IndentGuide>,
20975    active_indices: Option<Vec<usize>>,
20976    cx: &mut EditorTestContext,
20977) {
20978    let indent_guides = cx.update_editor(|editor, window, cx| {
20979        let snapshot = editor.snapshot(window, cx).display_snapshot;
20980        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20981            editor,
20982            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20983            true,
20984            &snapshot,
20985            cx,
20986        );
20987
20988        indent_guides.sort_by(|a, b| {
20989            a.depth.cmp(&b.depth).then(
20990                a.start_row
20991                    .cmp(&b.start_row)
20992                    .then(a.end_row.cmp(&b.end_row)),
20993            )
20994        });
20995        indent_guides
20996    });
20997
20998    if let Some(expected) = active_indices {
20999        let active_indices = cx.update_editor(|editor, window, cx| {
21000            let snapshot = editor.snapshot(window, cx).display_snapshot;
21001            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21002        });
21003
21004        assert_eq!(
21005            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21006            expected,
21007            "Active indent guide indices do not match"
21008        );
21009    }
21010
21011    assert_eq!(indent_guides, expected, "Indent guides do not match");
21012}
21013
21014fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21015    IndentGuide {
21016        buffer_id,
21017        start_row: MultiBufferRow(start_row),
21018        end_row: MultiBufferRow(end_row),
21019        depth,
21020        tab_size: 4,
21021        settings: IndentGuideSettings {
21022            enabled: true,
21023            line_width: 1,
21024            active_line_width: 1,
21025            coloring: IndentGuideColoring::default(),
21026            background_coloring: IndentGuideBackgroundColoring::default(),
21027        },
21028    }
21029}
21030
21031#[gpui::test]
21032async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21033    let (buffer_id, mut cx) = setup_indent_guides_editor(
21034        &"
21035        fn main() {
21036            let a = 1;
21037        }"
21038        .unindent(),
21039        cx,
21040    )
21041    .await;
21042
21043    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21044}
21045
21046#[gpui::test]
21047async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21048    let (buffer_id, mut cx) = setup_indent_guides_editor(
21049        &"
21050        fn main() {
21051            let a = 1;
21052            let b = 2;
21053        }"
21054        .unindent(),
21055        cx,
21056    )
21057    .await;
21058
21059    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21060}
21061
21062#[gpui::test]
21063async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21064    let (buffer_id, mut cx) = setup_indent_guides_editor(
21065        &"
21066        fn main() {
21067            let a = 1;
21068            if a == 3 {
21069                let b = 2;
21070            } else {
21071                let c = 3;
21072            }
21073        }"
21074        .unindent(),
21075        cx,
21076    )
21077    .await;
21078
21079    assert_indent_guides(
21080        0..8,
21081        vec![
21082            indent_guide(buffer_id, 1, 6, 0),
21083            indent_guide(buffer_id, 3, 3, 1),
21084            indent_guide(buffer_id, 5, 5, 1),
21085        ],
21086        None,
21087        &mut cx,
21088    );
21089}
21090
21091#[gpui::test]
21092async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21093    let (buffer_id, mut cx) = setup_indent_guides_editor(
21094        &"
21095        fn main() {
21096            let a = 1;
21097                let b = 2;
21098            let c = 3;
21099        }"
21100        .unindent(),
21101        cx,
21102    )
21103    .await;
21104
21105    assert_indent_guides(
21106        0..5,
21107        vec![
21108            indent_guide(buffer_id, 1, 3, 0),
21109            indent_guide(buffer_id, 2, 2, 1),
21110        ],
21111        None,
21112        &mut cx,
21113    );
21114}
21115
21116#[gpui::test]
21117async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21118    let (buffer_id, mut cx) = setup_indent_guides_editor(
21119        &"
21120        fn main() {
21121            let a = 1;
21122
21123            let c = 3;
21124        }"
21125        .unindent(),
21126        cx,
21127    )
21128    .await;
21129
21130    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21131}
21132
21133#[gpui::test]
21134async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21135    let (buffer_id, mut cx) = setup_indent_guides_editor(
21136        &"
21137        fn main() {
21138            let a = 1;
21139
21140            let c = 3;
21141
21142            if a == 3 {
21143                let b = 2;
21144            } else {
21145                let c = 3;
21146            }
21147        }"
21148        .unindent(),
21149        cx,
21150    )
21151    .await;
21152
21153    assert_indent_guides(
21154        0..11,
21155        vec![
21156            indent_guide(buffer_id, 1, 9, 0),
21157            indent_guide(buffer_id, 6, 6, 1),
21158            indent_guide(buffer_id, 8, 8, 1),
21159        ],
21160        None,
21161        &mut cx,
21162    );
21163}
21164
21165#[gpui::test]
21166async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21167    let (buffer_id, mut cx) = setup_indent_guides_editor(
21168        &"
21169        fn main() {
21170            let a = 1;
21171
21172            let c = 3;
21173
21174            if a == 3 {
21175                let b = 2;
21176            } else {
21177                let c = 3;
21178            }
21179        }"
21180        .unindent(),
21181        cx,
21182    )
21183    .await;
21184
21185    assert_indent_guides(
21186        1..11,
21187        vec![
21188            indent_guide(buffer_id, 1, 9, 0),
21189            indent_guide(buffer_id, 6, 6, 1),
21190            indent_guide(buffer_id, 8, 8, 1),
21191        ],
21192        None,
21193        &mut cx,
21194    );
21195}
21196
21197#[gpui::test]
21198async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21199    let (buffer_id, mut cx) = setup_indent_guides_editor(
21200        &"
21201        fn main() {
21202            let a = 1;
21203
21204            let c = 3;
21205
21206            if a == 3 {
21207                let b = 2;
21208            } else {
21209                let c = 3;
21210            }
21211        }"
21212        .unindent(),
21213        cx,
21214    )
21215    .await;
21216
21217    assert_indent_guides(
21218        1..10,
21219        vec![
21220            indent_guide(buffer_id, 1, 9, 0),
21221            indent_guide(buffer_id, 6, 6, 1),
21222            indent_guide(buffer_id, 8, 8, 1),
21223        ],
21224        None,
21225        &mut cx,
21226    );
21227}
21228
21229#[gpui::test]
21230async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21231    let (buffer_id, mut cx) = setup_indent_guides_editor(
21232        &"
21233        fn main() {
21234            if a {
21235                b(
21236                    c,
21237                    d,
21238                )
21239            } else {
21240                e(
21241                    f
21242                )
21243            }
21244        }"
21245        .unindent(),
21246        cx,
21247    )
21248    .await;
21249
21250    assert_indent_guides(
21251        0..11,
21252        vec![
21253            indent_guide(buffer_id, 1, 10, 0),
21254            indent_guide(buffer_id, 2, 5, 1),
21255            indent_guide(buffer_id, 7, 9, 1),
21256            indent_guide(buffer_id, 3, 4, 2),
21257            indent_guide(buffer_id, 8, 8, 2),
21258        ],
21259        None,
21260        &mut cx,
21261    );
21262
21263    cx.update_editor(|editor, window, cx| {
21264        editor.fold_at(MultiBufferRow(2), window, cx);
21265        assert_eq!(
21266            editor.display_text(cx),
21267            "
21268            fn main() {
21269                if a {
21270                    b(⋯
21271                    )
21272                } else {
21273                    e(
21274                        f
21275                    )
21276                }
21277            }"
21278            .unindent()
21279        );
21280    });
21281
21282    assert_indent_guides(
21283        0..11,
21284        vec![
21285            indent_guide(buffer_id, 1, 10, 0),
21286            indent_guide(buffer_id, 2, 5, 1),
21287            indent_guide(buffer_id, 7, 9, 1),
21288            indent_guide(buffer_id, 8, 8, 2),
21289        ],
21290        None,
21291        &mut cx,
21292    );
21293}
21294
21295#[gpui::test]
21296async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21297    let (buffer_id, mut cx) = setup_indent_guides_editor(
21298        &"
21299        block1
21300            block2
21301                block3
21302                    block4
21303            block2
21304        block1
21305        block1"
21306            .unindent(),
21307        cx,
21308    )
21309    .await;
21310
21311    assert_indent_guides(
21312        1..10,
21313        vec![
21314            indent_guide(buffer_id, 1, 4, 0),
21315            indent_guide(buffer_id, 2, 3, 1),
21316            indent_guide(buffer_id, 3, 3, 2),
21317        ],
21318        None,
21319        &mut cx,
21320    );
21321}
21322
21323#[gpui::test]
21324async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21325    let (buffer_id, mut cx) = setup_indent_guides_editor(
21326        &"
21327        block1
21328            block2
21329                block3
21330
21331        block1
21332        block1"
21333            .unindent(),
21334        cx,
21335    )
21336    .await;
21337
21338    assert_indent_guides(
21339        0..6,
21340        vec![
21341            indent_guide(buffer_id, 1, 2, 0),
21342            indent_guide(buffer_id, 2, 2, 1),
21343        ],
21344        None,
21345        &mut cx,
21346    );
21347}
21348
21349#[gpui::test]
21350async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21351    let (buffer_id, mut cx) = setup_indent_guides_editor(
21352        &"
21353        function component() {
21354        \treturn (
21355        \t\t\t
21356        \t\t<div>
21357        \t\t\t<abc></abc>
21358        \t\t</div>
21359        \t)
21360        }"
21361        .unindent(),
21362        cx,
21363    )
21364    .await;
21365
21366    assert_indent_guides(
21367        0..8,
21368        vec![
21369            indent_guide(buffer_id, 1, 6, 0),
21370            indent_guide(buffer_id, 2, 5, 1),
21371            indent_guide(buffer_id, 4, 4, 2),
21372        ],
21373        None,
21374        &mut cx,
21375    );
21376}
21377
21378#[gpui::test]
21379async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21380    let (buffer_id, mut cx) = setup_indent_guides_editor(
21381        &"
21382        function component() {
21383        \treturn (
21384        \t
21385        \t\t<div>
21386        \t\t\t<abc></abc>
21387        \t\t</div>
21388        \t)
21389        }"
21390        .unindent(),
21391        cx,
21392    )
21393    .await;
21394
21395    assert_indent_guides(
21396        0..8,
21397        vec![
21398            indent_guide(buffer_id, 1, 6, 0),
21399            indent_guide(buffer_id, 2, 5, 1),
21400            indent_guide(buffer_id, 4, 4, 2),
21401        ],
21402        None,
21403        &mut cx,
21404    );
21405}
21406
21407#[gpui::test]
21408async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21409    let (buffer_id, mut cx) = setup_indent_guides_editor(
21410        &"
21411        block1
21412
21413
21414
21415            block2
21416        "
21417        .unindent(),
21418        cx,
21419    )
21420    .await;
21421
21422    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21423}
21424
21425#[gpui::test]
21426async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21427    let (buffer_id, mut cx) = setup_indent_guides_editor(
21428        &"
21429        def a:
21430        \tb = 3
21431        \tif True:
21432        \t\tc = 4
21433        \t\td = 5
21434        \tprint(b)
21435        "
21436        .unindent(),
21437        cx,
21438    )
21439    .await;
21440
21441    assert_indent_guides(
21442        0..6,
21443        vec![
21444            indent_guide(buffer_id, 1, 5, 0),
21445            indent_guide(buffer_id, 3, 4, 1),
21446        ],
21447        None,
21448        &mut cx,
21449    );
21450}
21451
21452#[gpui::test]
21453async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21454    let (buffer_id, mut cx) = setup_indent_guides_editor(
21455        &"
21456    fn main() {
21457        let a = 1;
21458    }"
21459        .unindent(),
21460        cx,
21461    )
21462    .await;
21463
21464    cx.update_editor(|editor, window, cx| {
21465        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21466            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21467        });
21468    });
21469
21470    assert_indent_guides(
21471        0..3,
21472        vec![indent_guide(buffer_id, 1, 1, 0)],
21473        Some(vec![0]),
21474        &mut cx,
21475    );
21476}
21477
21478#[gpui::test]
21479async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21480    let (buffer_id, mut cx) = setup_indent_guides_editor(
21481        &"
21482    fn main() {
21483        if 1 == 2 {
21484            let a = 1;
21485        }
21486    }"
21487        .unindent(),
21488        cx,
21489    )
21490    .await;
21491
21492    cx.update_editor(|editor, window, cx| {
21493        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21494            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21495        });
21496    });
21497
21498    assert_indent_guides(
21499        0..4,
21500        vec![
21501            indent_guide(buffer_id, 1, 3, 0),
21502            indent_guide(buffer_id, 2, 2, 1),
21503        ],
21504        Some(vec![1]),
21505        &mut cx,
21506    );
21507
21508    cx.update_editor(|editor, window, cx| {
21509        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21510            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21511        });
21512    });
21513
21514    assert_indent_guides(
21515        0..4,
21516        vec![
21517            indent_guide(buffer_id, 1, 3, 0),
21518            indent_guide(buffer_id, 2, 2, 1),
21519        ],
21520        Some(vec![1]),
21521        &mut cx,
21522    );
21523
21524    cx.update_editor(|editor, window, cx| {
21525        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21526            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21527        });
21528    });
21529
21530    assert_indent_guides(
21531        0..4,
21532        vec![
21533            indent_guide(buffer_id, 1, 3, 0),
21534            indent_guide(buffer_id, 2, 2, 1),
21535        ],
21536        Some(vec![0]),
21537        &mut cx,
21538    );
21539}
21540
21541#[gpui::test]
21542async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21543    let (buffer_id, mut cx) = setup_indent_guides_editor(
21544        &"
21545    fn main() {
21546        let a = 1;
21547
21548        let b = 2;
21549    }"
21550        .unindent(),
21551        cx,
21552    )
21553    .await;
21554
21555    cx.update_editor(|editor, window, cx| {
21556        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21557            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21558        });
21559    });
21560
21561    assert_indent_guides(
21562        0..5,
21563        vec![indent_guide(buffer_id, 1, 3, 0)],
21564        Some(vec![0]),
21565        &mut cx,
21566    );
21567}
21568
21569#[gpui::test]
21570async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21571    let (buffer_id, mut cx) = setup_indent_guides_editor(
21572        &"
21573    def m:
21574        a = 1
21575        pass"
21576            .unindent(),
21577        cx,
21578    )
21579    .await;
21580
21581    cx.update_editor(|editor, window, cx| {
21582        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21583            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21584        });
21585    });
21586
21587    assert_indent_guides(
21588        0..3,
21589        vec![indent_guide(buffer_id, 1, 2, 0)],
21590        Some(vec![0]),
21591        &mut cx,
21592    );
21593}
21594
21595#[gpui::test]
21596async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21597    init_test(cx, |_| {});
21598    let mut cx = EditorTestContext::new(cx).await;
21599    let text = indoc! {
21600        "
21601        impl A {
21602            fn b() {
21603                0;
21604                3;
21605                5;
21606                6;
21607                7;
21608            }
21609        }
21610        "
21611    };
21612    let base_text = indoc! {
21613        "
21614        impl A {
21615            fn b() {
21616                0;
21617                1;
21618                2;
21619                3;
21620                4;
21621            }
21622            fn c() {
21623                5;
21624                6;
21625                7;
21626            }
21627        }
21628        "
21629    };
21630
21631    cx.update_editor(|editor, window, cx| {
21632        editor.set_text(text, window, cx);
21633
21634        editor.buffer().update(cx, |multibuffer, cx| {
21635            let buffer = multibuffer.as_singleton().unwrap();
21636            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21637
21638            multibuffer.set_all_diff_hunks_expanded(cx);
21639            multibuffer.add_diff(diff, cx);
21640
21641            buffer.read(cx).remote_id()
21642        })
21643    });
21644    cx.run_until_parked();
21645
21646    cx.assert_state_with_diff(
21647        indoc! { "
21648          impl A {
21649              fn b() {
21650                  0;
21651        -         1;
21652        -         2;
21653                  3;
21654        -         4;
21655        -     }
21656        -     fn c() {
21657                  5;
21658                  6;
21659                  7;
21660              }
21661          }
21662          ˇ"
21663        }
21664        .to_string(),
21665    );
21666
21667    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21668        editor
21669            .snapshot(window, cx)
21670            .buffer_snapshot()
21671            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21672            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21673            .collect::<Vec<_>>()
21674    });
21675    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21676    assert_eq!(
21677        actual_guides,
21678        vec![
21679            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21680            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21681            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21682        ]
21683    );
21684}
21685
21686#[gpui::test]
21687async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21688    init_test(cx, |_| {});
21689    let mut cx = EditorTestContext::new(cx).await;
21690
21691    let diff_base = r#"
21692        a
21693        b
21694        c
21695        "#
21696    .unindent();
21697
21698    cx.set_state(
21699        &r#"
21700        ˇA
21701        b
21702        C
21703        "#
21704        .unindent(),
21705    );
21706    cx.set_head_text(&diff_base);
21707    cx.update_editor(|editor, window, cx| {
21708        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21709    });
21710    executor.run_until_parked();
21711
21712    let both_hunks_expanded = r#"
21713        - a
21714        + ˇA
21715          b
21716        - c
21717        + C
21718        "#
21719    .unindent();
21720
21721    cx.assert_state_with_diff(both_hunks_expanded.clone());
21722
21723    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21724        let snapshot = editor.snapshot(window, cx);
21725        let hunks = editor
21726            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21727            .collect::<Vec<_>>();
21728        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21729        hunks
21730            .into_iter()
21731            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21732            .collect::<Vec<_>>()
21733    });
21734    assert_eq!(hunk_ranges.len(), 2);
21735
21736    cx.update_editor(|editor, _, cx| {
21737        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21738    });
21739    executor.run_until_parked();
21740
21741    let second_hunk_expanded = r#"
21742          ˇA
21743          b
21744        - c
21745        + C
21746        "#
21747    .unindent();
21748
21749    cx.assert_state_with_diff(second_hunk_expanded);
21750
21751    cx.update_editor(|editor, _, cx| {
21752        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21753    });
21754    executor.run_until_parked();
21755
21756    cx.assert_state_with_diff(both_hunks_expanded.clone());
21757
21758    cx.update_editor(|editor, _, cx| {
21759        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21760    });
21761    executor.run_until_parked();
21762
21763    let first_hunk_expanded = r#"
21764        - a
21765        + ˇA
21766          b
21767          C
21768        "#
21769    .unindent();
21770
21771    cx.assert_state_with_diff(first_hunk_expanded);
21772
21773    cx.update_editor(|editor, _, cx| {
21774        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21775    });
21776    executor.run_until_parked();
21777
21778    cx.assert_state_with_diff(both_hunks_expanded);
21779
21780    cx.set_state(
21781        &r#"
21782        ˇA
21783        b
21784        "#
21785        .unindent(),
21786    );
21787    cx.run_until_parked();
21788
21789    // TODO this cursor position seems bad
21790    cx.assert_state_with_diff(
21791        r#"
21792        - ˇa
21793        + A
21794          b
21795        "#
21796        .unindent(),
21797    );
21798
21799    cx.update_editor(|editor, window, cx| {
21800        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21801    });
21802
21803    cx.assert_state_with_diff(
21804        r#"
21805            - ˇa
21806            + A
21807              b
21808            - c
21809            "#
21810        .unindent(),
21811    );
21812
21813    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21814        let snapshot = editor.snapshot(window, cx);
21815        let hunks = editor
21816            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21817            .collect::<Vec<_>>();
21818        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21819        hunks
21820            .into_iter()
21821            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21822            .collect::<Vec<_>>()
21823    });
21824    assert_eq!(hunk_ranges.len(), 2);
21825
21826    cx.update_editor(|editor, _, cx| {
21827        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21828    });
21829    executor.run_until_parked();
21830
21831    cx.assert_state_with_diff(
21832        r#"
21833        - ˇa
21834        + A
21835          b
21836        "#
21837        .unindent(),
21838    );
21839}
21840
21841#[gpui::test]
21842async fn test_toggle_deletion_hunk_at_start_of_file(
21843    executor: BackgroundExecutor,
21844    cx: &mut TestAppContext,
21845) {
21846    init_test(cx, |_| {});
21847    let mut cx = EditorTestContext::new(cx).await;
21848
21849    let diff_base = r#"
21850        a
21851        b
21852        c
21853        "#
21854    .unindent();
21855
21856    cx.set_state(
21857        &r#"
21858        ˇb
21859        c
21860        "#
21861        .unindent(),
21862    );
21863    cx.set_head_text(&diff_base);
21864    cx.update_editor(|editor, window, cx| {
21865        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21866    });
21867    executor.run_until_parked();
21868
21869    let hunk_expanded = r#"
21870        - a
21871          ˇb
21872          c
21873        "#
21874    .unindent();
21875
21876    cx.assert_state_with_diff(hunk_expanded.clone());
21877
21878    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21879        let snapshot = editor.snapshot(window, cx);
21880        let hunks = editor
21881            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21882            .collect::<Vec<_>>();
21883        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21884        hunks
21885            .into_iter()
21886            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21887            .collect::<Vec<_>>()
21888    });
21889    assert_eq!(hunk_ranges.len(), 1);
21890
21891    cx.update_editor(|editor, _, cx| {
21892        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21893    });
21894    executor.run_until_parked();
21895
21896    let hunk_collapsed = r#"
21897          ˇb
21898          c
21899        "#
21900    .unindent();
21901
21902    cx.assert_state_with_diff(hunk_collapsed);
21903
21904    cx.update_editor(|editor, _, cx| {
21905        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21906    });
21907    executor.run_until_parked();
21908
21909    cx.assert_state_with_diff(hunk_expanded);
21910}
21911
21912#[gpui::test]
21913async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21914    init_test(cx, |_| {});
21915
21916    let fs = FakeFs::new(cx.executor());
21917    fs.insert_tree(
21918        path!("/test"),
21919        json!({
21920            ".git": {},
21921            "file-1": "ONE\n",
21922            "file-2": "TWO\n",
21923            "file-3": "THREE\n",
21924        }),
21925    )
21926    .await;
21927
21928    fs.set_head_for_repo(
21929        path!("/test/.git").as_ref(),
21930        &[
21931            ("file-1", "one\n".into()),
21932            ("file-2", "two\n".into()),
21933            ("file-3", "three\n".into()),
21934        ],
21935        "deadbeef",
21936    );
21937
21938    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21939    let mut buffers = vec![];
21940    for i in 1..=3 {
21941        let buffer = project
21942            .update(cx, |project, cx| {
21943                let path = format!(path!("/test/file-{}"), i);
21944                project.open_local_buffer(path, cx)
21945            })
21946            .await
21947            .unwrap();
21948        buffers.push(buffer);
21949    }
21950
21951    let multibuffer = cx.new(|cx| {
21952        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21953        multibuffer.set_all_diff_hunks_expanded(cx);
21954        for buffer in &buffers {
21955            let snapshot = buffer.read(cx).snapshot();
21956            multibuffer.set_excerpts_for_path(
21957                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21958                buffer.clone(),
21959                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21960                2,
21961                cx,
21962            );
21963        }
21964        multibuffer
21965    });
21966
21967    let editor = cx.add_window(|window, cx| {
21968        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21969    });
21970    cx.run_until_parked();
21971
21972    let snapshot = editor
21973        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21974        .unwrap();
21975    let hunks = snapshot
21976        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21977        .map(|hunk| match hunk {
21978            DisplayDiffHunk::Unfolded {
21979                display_row_range, ..
21980            } => display_row_range,
21981            DisplayDiffHunk::Folded { .. } => unreachable!(),
21982        })
21983        .collect::<Vec<_>>();
21984    assert_eq!(
21985        hunks,
21986        [
21987            DisplayRow(2)..DisplayRow(4),
21988            DisplayRow(7)..DisplayRow(9),
21989            DisplayRow(12)..DisplayRow(14),
21990        ]
21991    );
21992}
21993
21994#[gpui::test]
21995async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21996    init_test(cx, |_| {});
21997
21998    let mut cx = EditorTestContext::new(cx).await;
21999    cx.set_head_text(indoc! { "
22000        one
22001        two
22002        three
22003        four
22004        five
22005        "
22006    });
22007    cx.set_index_text(indoc! { "
22008        one
22009        two
22010        three
22011        four
22012        five
22013        "
22014    });
22015    cx.set_state(indoc! {"
22016        one
22017        TWO
22018        ˇTHREE
22019        FOUR
22020        five
22021    "});
22022    cx.run_until_parked();
22023    cx.update_editor(|editor, window, cx| {
22024        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22025    });
22026    cx.run_until_parked();
22027    cx.assert_index_text(Some(indoc! {"
22028        one
22029        TWO
22030        THREE
22031        FOUR
22032        five
22033    "}));
22034    cx.set_state(indoc! { "
22035        one
22036        TWO
22037        ˇTHREE-HUNDRED
22038        FOUR
22039        five
22040    "});
22041    cx.run_until_parked();
22042    cx.update_editor(|editor, window, cx| {
22043        let snapshot = editor.snapshot(window, cx);
22044        let hunks = editor
22045            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22046            .collect::<Vec<_>>();
22047        assert_eq!(hunks.len(), 1);
22048        assert_eq!(
22049            hunks[0].status(),
22050            DiffHunkStatus {
22051                kind: DiffHunkStatusKind::Modified,
22052                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22053            }
22054        );
22055
22056        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22057    });
22058    cx.run_until_parked();
22059    cx.assert_index_text(Some(indoc! {"
22060        one
22061        TWO
22062        THREE-HUNDRED
22063        FOUR
22064        five
22065    "}));
22066}
22067
22068#[gpui::test]
22069fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22070    init_test(cx, |_| {});
22071
22072    let editor = cx.add_window(|window, cx| {
22073        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22074        build_editor(buffer, window, cx)
22075    });
22076
22077    let render_args = Arc::new(Mutex::new(None));
22078    let snapshot = editor
22079        .update(cx, |editor, window, cx| {
22080            let snapshot = editor.buffer().read(cx).snapshot(cx);
22081            let range =
22082                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22083
22084            struct RenderArgs {
22085                row: MultiBufferRow,
22086                folded: bool,
22087                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22088            }
22089
22090            let crease = Crease::inline(
22091                range,
22092                FoldPlaceholder::test(),
22093                {
22094                    let toggle_callback = render_args.clone();
22095                    move |row, folded, callback, _window, _cx| {
22096                        *toggle_callback.lock() = Some(RenderArgs {
22097                            row,
22098                            folded,
22099                            callback,
22100                        });
22101                        div()
22102                    }
22103                },
22104                |_row, _folded, _window, _cx| div(),
22105            );
22106
22107            editor.insert_creases(Some(crease), cx);
22108            let snapshot = editor.snapshot(window, cx);
22109            let _div =
22110                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22111            snapshot
22112        })
22113        .unwrap();
22114
22115    let render_args = render_args.lock().take().unwrap();
22116    assert_eq!(render_args.row, MultiBufferRow(1));
22117    assert!(!render_args.folded);
22118    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22119
22120    cx.update_window(*editor, |_, window, cx| {
22121        (render_args.callback)(true, window, cx)
22122    })
22123    .unwrap();
22124    let snapshot = editor
22125        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22126        .unwrap();
22127    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22128
22129    cx.update_window(*editor, |_, window, cx| {
22130        (render_args.callback)(false, window, cx)
22131    })
22132    .unwrap();
22133    let snapshot = editor
22134        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22135        .unwrap();
22136    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22137}
22138
22139#[gpui::test]
22140async fn test_input_text(cx: &mut TestAppContext) {
22141    init_test(cx, |_| {});
22142    let mut cx = EditorTestContext::new(cx).await;
22143
22144    cx.set_state(
22145        &r#"ˇone
22146        two
22147
22148        three
22149        fourˇ
22150        five
22151
22152        siˇx"#
22153            .unindent(),
22154    );
22155
22156    cx.dispatch_action(HandleInput(String::new()));
22157    cx.assert_editor_state(
22158        &r#"ˇone
22159        two
22160
22161        three
22162        fourˇ
22163        five
22164
22165        siˇx"#
22166            .unindent(),
22167    );
22168
22169    cx.dispatch_action(HandleInput("AAAA".to_string()));
22170    cx.assert_editor_state(
22171        &r#"AAAAˇone
22172        two
22173
22174        three
22175        fourAAAAˇ
22176        five
22177
22178        siAAAAˇx"#
22179            .unindent(),
22180    );
22181}
22182
22183#[gpui::test]
22184async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22185    init_test(cx, |_| {});
22186
22187    let mut cx = EditorTestContext::new(cx).await;
22188    cx.set_state(
22189        r#"let foo = 1;
22190let foo = 2;
22191let foo = 3;
22192let fooˇ = 4;
22193let foo = 5;
22194let foo = 6;
22195let foo = 7;
22196let foo = 8;
22197let foo = 9;
22198let foo = 10;
22199let foo = 11;
22200let foo = 12;
22201let foo = 13;
22202let foo = 14;
22203let foo = 15;"#,
22204    );
22205
22206    cx.update_editor(|e, window, cx| {
22207        assert_eq!(
22208            e.next_scroll_position,
22209            NextScrollCursorCenterTopBottom::Center,
22210            "Default next scroll direction is center",
22211        );
22212
22213        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22214        assert_eq!(
22215            e.next_scroll_position,
22216            NextScrollCursorCenterTopBottom::Top,
22217            "After center, next scroll direction should be top",
22218        );
22219
22220        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22221        assert_eq!(
22222            e.next_scroll_position,
22223            NextScrollCursorCenterTopBottom::Bottom,
22224            "After top, next scroll direction should be bottom",
22225        );
22226
22227        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22228        assert_eq!(
22229            e.next_scroll_position,
22230            NextScrollCursorCenterTopBottom::Center,
22231            "After bottom, scrolling should start over",
22232        );
22233
22234        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22235        assert_eq!(
22236            e.next_scroll_position,
22237            NextScrollCursorCenterTopBottom::Top,
22238            "Scrolling continues if retriggered fast enough"
22239        );
22240    });
22241
22242    cx.executor()
22243        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22244    cx.executor().run_until_parked();
22245    cx.update_editor(|e, _, _| {
22246        assert_eq!(
22247            e.next_scroll_position,
22248            NextScrollCursorCenterTopBottom::Center,
22249            "If scrolling is not triggered fast enough, it should reset"
22250        );
22251    });
22252}
22253
22254#[gpui::test]
22255async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22256    init_test(cx, |_| {});
22257    let mut cx = EditorLspTestContext::new_rust(
22258        lsp::ServerCapabilities {
22259            definition_provider: Some(lsp::OneOf::Left(true)),
22260            references_provider: Some(lsp::OneOf::Left(true)),
22261            ..lsp::ServerCapabilities::default()
22262        },
22263        cx,
22264    )
22265    .await;
22266
22267    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22268        let go_to_definition = cx
22269            .lsp
22270            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22271                move |params, _| async move {
22272                    if empty_go_to_definition {
22273                        Ok(None)
22274                    } else {
22275                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22276                            uri: params.text_document_position_params.text_document.uri,
22277                            range: lsp::Range::new(
22278                                lsp::Position::new(4, 3),
22279                                lsp::Position::new(4, 6),
22280                            ),
22281                        })))
22282                    }
22283                },
22284            );
22285        let references = cx
22286            .lsp
22287            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22288                Ok(Some(vec![lsp::Location {
22289                    uri: params.text_document_position.text_document.uri,
22290                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22291                }]))
22292            });
22293        (go_to_definition, references)
22294    };
22295
22296    cx.set_state(
22297        &r#"fn one() {
22298            let mut a = ˇtwo();
22299        }
22300
22301        fn two() {}"#
22302            .unindent(),
22303    );
22304    set_up_lsp_handlers(false, &mut cx);
22305    let navigated = cx
22306        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22307        .await
22308        .expect("Failed to navigate to definition");
22309    assert_eq!(
22310        navigated,
22311        Navigated::Yes,
22312        "Should have navigated to definition from the GetDefinition response"
22313    );
22314    cx.assert_editor_state(
22315        &r#"fn one() {
22316            let mut a = two();
22317        }
22318
22319        fn «twoˇ»() {}"#
22320            .unindent(),
22321    );
22322
22323    let editors = cx.update_workspace(|workspace, _, cx| {
22324        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22325    });
22326    cx.update_editor(|_, _, test_editor_cx| {
22327        assert_eq!(
22328            editors.len(),
22329            1,
22330            "Initially, only one, test, editor should be open in the workspace"
22331        );
22332        assert_eq!(
22333            test_editor_cx.entity(),
22334            editors.last().expect("Asserted len is 1").clone()
22335        );
22336    });
22337
22338    set_up_lsp_handlers(true, &mut cx);
22339    let navigated = cx
22340        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22341        .await
22342        .expect("Failed to navigate to lookup references");
22343    assert_eq!(
22344        navigated,
22345        Navigated::Yes,
22346        "Should have navigated to references as a fallback after empty GoToDefinition response"
22347    );
22348    // We should not change the selections in the existing file,
22349    // if opening another milti buffer with the references
22350    cx.assert_editor_state(
22351        &r#"fn one() {
22352            let mut a = two();
22353        }
22354
22355        fn «twoˇ»() {}"#
22356            .unindent(),
22357    );
22358    let editors = cx.update_workspace(|workspace, _, cx| {
22359        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22360    });
22361    cx.update_editor(|_, _, test_editor_cx| {
22362        assert_eq!(
22363            editors.len(),
22364            2,
22365            "After falling back to references search, we open a new editor with the results"
22366        );
22367        let references_fallback_text = editors
22368            .into_iter()
22369            .find(|new_editor| *new_editor != test_editor_cx.entity())
22370            .expect("Should have one non-test editor now")
22371            .read(test_editor_cx)
22372            .text(test_editor_cx);
22373        assert_eq!(
22374            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22375            "Should use the range from the references response and not the GoToDefinition one"
22376        );
22377    });
22378}
22379
22380#[gpui::test]
22381async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22382    init_test(cx, |_| {});
22383    cx.update(|cx| {
22384        let mut editor_settings = EditorSettings::get_global(cx).clone();
22385        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22386        EditorSettings::override_global(editor_settings, cx);
22387    });
22388    let mut cx = EditorLspTestContext::new_rust(
22389        lsp::ServerCapabilities {
22390            definition_provider: Some(lsp::OneOf::Left(true)),
22391            references_provider: Some(lsp::OneOf::Left(true)),
22392            ..lsp::ServerCapabilities::default()
22393        },
22394        cx,
22395    )
22396    .await;
22397    let original_state = r#"fn one() {
22398        let mut a = ˇtwo();
22399    }
22400
22401    fn two() {}"#
22402        .unindent();
22403    cx.set_state(&original_state);
22404
22405    let mut go_to_definition = cx
22406        .lsp
22407        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22408            move |_, _| async move { Ok(None) },
22409        );
22410    let _references = cx
22411        .lsp
22412        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22413            panic!("Should not call for references with no go to definition fallback")
22414        });
22415
22416    let navigated = cx
22417        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22418        .await
22419        .expect("Failed to navigate to lookup references");
22420    go_to_definition
22421        .next()
22422        .await
22423        .expect("Should have called the go_to_definition handler");
22424
22425    assert_eq!(
22426        navigated,
22427        Navigated::No,
22428        "Should have navigated to references as a fallback after empty GoToDefinition response"
22429    );
22430    cx.assert_editor_state(&original_state);
22431    let editors = cx.update_workspace(|workspace, _, cx| {
22432        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22433    });
22434    cx.update_editor(|_, _, _| {
22435        assert_eq!(
22436            editors.len(),
22437            1,
22438            "After unsuccessful fallback, no other editor should have been opened"
22439        );
22440    });
22441}
22442
22443#[gpui::test]
22444async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22445    init_test(cx, |_| {});
22446    let mut cx = EditorLspTestContext::new_rust(
22447        lsp::ServerCapabilities {
22448            references_provider: Some(lsp::OneOf::Left(true)),
22449            ..lsp::ServerCapabilities::default()
22450        },
22451        cx,
22452    )
22453    .await;
22454
22455    cx.set_state(
22456        &r#"
22457        fn one() {
22458            let mut a = two();
22459        }
22460
22461        fn ˇtwo() {}"#
22462            .unindent(),
22463    );
22464    cx.lsp
22465        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22466            Ok(Some(vec![
22467                lsp::Location {
22468                    uri: params.text_document_position.text_document.uri.clone(),
22469                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22470                },
22471                lsp::Location {
22472                    uri: params.text_document_position.text_document.uri,
22473                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22474                },
22475            ]))
22476        });
22477    let navigated = cx
22478        .update_editor(|editor, window, cx| {
22479            editor.find_all_references(&FindAllReferences, window, cx)
22480        })
22481        .unwrap()
22482        .await
22483        .expect("Failed to navigate to references");
22484    assert_eq!(
22485        navigated,
22486        Navigated::Yes,
22487        "Should have navigated to references from the FindAllReferences response"
22488    );
22489    cx.assert_editor_state(
22490        &r#"fn one() {
22491            let mut a = two();
22492        }
22493
22494        fn ˇtwo() {}"#
22495            .unindent(),
22496    );
22497
22498    let editors = cx.update_workspace(|workspace, _, cx| {
22499        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22500    });
22501    cx.update_editor(|_, _, _| {
22502        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22503    });
22504
22505    cx.set_state(
22506        &r#"fn one() {
22507            let mut a = ˇtwo();
22508        }
22509
22510        fn two() {}"#
22511            .unindent(),
22512    );
22513    let navigated = cx
22514        .update_editor(|editor, window, cx| {
22515            editor.find_all_references(&FindAllReferences, window, cx)
22516        })
22517        .unwrap()
22518        .await
22519        .expect("Failed to navigate to references");
22520    assert_eq!(
22521        navigated,
22522        Navigated::Yes,
22523        "Should have navigated to references from the FindAllReferences response"
22524    );
22525    cx.assert_editor_state(
22526        &r#"fn one() {
22527            let mut a = ˇtwo();
22528        }
22529
22530        fn two() {}"#
22531            .unindent(),
22532    );
22533    let editors = cx.update_workspace(|workspace, _, cx| {
22534        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22535    });
22536    cx.update_editor(|_, _, _| {
22537        assert_eq!(
22538            editors.len(),
22539            2,
22540            "should have re-used the previous multibuffer"
22541        );
22542    });
22543
22544    cx.set_state(
22545        &r#"fn one() {
22546            let mut a = ˇtwo();
22547        }
22548        fn three() {}
22549        fn two() {}"#
22550            .unindent(),
22551    );
22552    cx.lsp
22553        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22554            Ok(Some(vec![
22555                lsp::Location {
22556                    uri: params.text_document_position.text_document.uri.clone(),
22557                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22558                },
22559                lsp::Location {
22560                    uri: params.text_document_position.text_document.uri,
22561                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22562                },
22563            ]))
22564        });
22565    let navigated = cx
22566        .update_editor(|editor, window, cx| {
22567            editor.find_all_references(&FindAllReferences, window, cx)
22568        })
22569        .unwrap()
22570        .await
22571        .expect("Failed to navigate to references");
22572    assert_eq!(
22573        navigated,
22574        Navigated::Yes,
22575        "Should have navigated to references from the FindAllReferences response"
22576    );
22577    cx.assert_editor_state(
22578        &r#"fn one() {
22579                let mut a = ˇtwo();
22580            }
22581            fn three() {}
22582            fn two() {}"#
22583            .unindent(),
22584    );
22585    let editors = cx.update_workspace(|workspace, _, cx| {
22586        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22587    });
22588    cx.update_editor(|_, _, _| {
22589        assert_eq!(
22590            editors.len(),
22591            3,
22592            "should have used a new multibuffer as offsets changed"
22593        );
22594    });
22595}
22596#[gpui::test]
22597async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22598    init_test(cx, |_| {});
22599
22600    let language = Arc::new(Language::new(
22601        LanguageConfig::default(),
22602        Some(tree_sitter_rust::LANGUAGE.into()),
22603    ));
22604
22605    let text = r#"
22606        #[cfg(test)]
22607        mod tests() {
22608            #[test]
22609            fn runnable_1() {
22610                let a = 1;
22611            }
22612
22613            #[test]
22614            fn runnable_2() {
22615                let a = 1;
22616                let b = 2;
22617            }
22618        }
22619    "#
22620    .unindent();
22621
22622    let fs = FakeFs::new(cx.executor());
22623    fs.insert_file("/file.rs", Default::default()).await;
22624
22625    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22626    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22627    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22628    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22629    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22630
22631    let editor = cx.new_window_entity(|window, cx| {
22632        Editor::new(
22633            EditorMode::full(),
22634            multi_buffer,
22635            Some(project.clone()),
22636            window,
22637            cx,
22638        )
22639    });
22640
22641    editor.update_in(cx, |editor, window, cx| {
22642        let snapshot = editor.buffer().read(cx).snapshot(cx);
22643        editor.tasks.insert(
22644            (buffer.read(cx).remote_id(), 3),
22645            RunnableTasks {
22646                templates: vec![],
22647                offset: snapshot.anchor_before(MultiBufferOffset(43)),
22648                column: 0,
22649                extra_variables: HashMap::default(),
22650                context_range: BufferOffset(43)..BufferOffset(85),
22651            },
22652        );
22653        editor.tasks.insert(
22654            (buffer.read(cx).remote_id(), 8),
22655            RunnableTasks {
22656                templates: vec![],
22657                offset: snapshot.anchor_before(MultiBufferOffset(86)),
22658                column: 0,
22659                extra_variables: HashMap::default(),
22660                context_range: BufferOffset(86)..BufferOffset(191),
22661            },
22662        );
22663
22664        // Test finding task when cursor is inside function body
22665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22666            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22667        });
22668        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22669        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22670
22671        // Test finding task when cursor is on function name
22672        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22673            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22674        });
22675        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22676        assert_eq!(row, 8, "Should find task when cursor is on function name");
22677    });
22678}
22679
22680#[gpui::test]
22681async fn test_folding_buffers(cx: &mut TestAppContext) {
22682    init_test(cx, |_| {});
22683
22684    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22685    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22686    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22687
22688    let fs = FakeFs::new(cx.executor());
22689    fs.insert_tree(
22690        path!("/a"),
22691        json!({
22692            "first.rs": sample_text_1,
22693            "second.rs": sample_text_2,
22694            "third.rs": sample_text_3,
22695        }),
22696    )
22697    .await;
22698    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22699    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22700    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22701    let worktree = project.update(cx, |project, cx| {
22702        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22703        assert_eq!(worktrees.len(), 1);
22704        worktrees.pop().unwrap()
22705    });
22706    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22707
22708    let buffer_1 = project
22709        .update(cx, |project, cx| {
22710            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22711        })
22712        .await
22713        .unwrap();
22714    let buffer_2 = project
22715        .update(cx, |project, cx| {
22716            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22717        })
22718        .await
22719        .unwrap();
22720    let buffer_3 = project
22721        .update(cx, |project, cx| {
22722            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22723        })
22724        .await
22725        .unwrap();
22726
22727    let multi_buffer = cx.new(|cx| {
22728        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22729        multi_buffer.push_excerpts(
22730            buffer_1.clone(),
22731            [
22732                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22733                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22734                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22735            ],
22736            cx,
22737        );
22738        multi_buffer.push_excerpts(
22739            buffer_2.clone(),
22740            [
22741                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22742                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22743                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22744            ],
22745            cx,
22746        );
22747        multi_buffer.push_excerpts(
22748            buffer_3.clone(),
22749            [
22750                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22751                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22752                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22753            ],
22754            cx,
22755        );
22756        multi_buffer
22757    });
22758    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22759        Editor::new(
22760            EditorMode::full(),
22761            multi_buffer.clone(),
22762            Some(project.clone()),
22763            window,
22764            cx,
22765        )
22766    });
22767
22768    assert_eq!(
22769        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22770        "\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",
22771    );
22772
22773    multi_buffer_editor.update(cx, |editor, cx| {
22774        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22775    });
22776    assert_eq!(
22777        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22778        "\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",
22779        "After folding the first buffer, its text should not be displayed"
22780    );
22781
22782    multi_buffer_editor.update(cx, |editor, cx| {
22783        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22784    });
22785    assert_eq!(
22786        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22787        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22788        "After folding the second buffer, its text should not be displayed"
22789    );
22790
22791    multi_buffer_editor.update(cx, |editor, cx| {
22792        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22793    });
22794    assert_eq!(
22795        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22796        "\n\n\n\n\n",
22797        "After folding the third buffer, its text should not be displayed"
22798    );
22799
22800    // Emulate selection inside the fold logic, that should work
22801    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22802        editor
22803            .snapshot(window, cx)
22804            .next_line_boundary(Point::new(0, 4));
22805    });
22806
22807    multi_buffer_editor.update(cx, |editor, cx| {
22808        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22809    });
22810    assert_eq!(
22811        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22812        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22813        "After unfolding the second buffer, its text should be displayed"
22814    );
22815
22816    // Typing inside of buffer 1 causes that buffer to be unfolded.
22817    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22818        assert_eq!(
22819            multi_buffer
22820                .read(cx)
22821                .snapshot(cx)
22822                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22823                .collect::<String>(),
22824            "bbbb"
22825        );
22826        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22827            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22828        });
22829        editor.handle_input("B", window, cx);
22830    });
22831
22832    assert_eq!(
22833        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22834        "\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",
22835        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22836    );
22837
22838    multi_buffer_editor.update(cx, |editor, cx| {
22839        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22840    });
22841    assert_eq!(
22842        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22843        "\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",
22844        "After unfolding the all buffers, all original text should be displayed"
22845    );
22846}
22847
22848#[gpui::test]
22849async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22850    init_test(cx, |_| {});
22851
22852    let sample_text_1 = "1111\n2222\n3333".to_string();
22853    let sample_text_2 = "4444\n5555\n6666".to_string();
22854    let sample_text_3 = "7777\n8888\n9999".to_string();
22855
22856    let fs = FakeFs::new(cx.executor());
22857    fs.insert_tree(
22858        path!("/a"),
22859        json!({
22860            "first.rs": sample_text_1,
22861            "second.rs": sample_text_2,
22862            "third.rs": sample_text_3,
22863        }),
22864    )
22865    .await;
22866    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22867    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22868    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22869    let worktree = project.update(cx, |project, cx| {
22870        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22871        assert_eq!(worktrees.len(), 1);
22872        worktrees.pop().unwrap()
22873    });
22874    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22875
22876    let buffer_1 = project
22877        .update(cx, |project, cx| {
22878            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22879        })
22880        .await
22881        .unwrap();
22882    let buffer_2 = project
22883        .update(cx, |project, cx| {
22884            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22885        })
22886        .await
22887        .unwrap();
22888    let buffer_3 = project
22889        .update(cx, |project, cx| {
22890            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22891        })
22892        .await
22893        .unwrap();
22894
22895    let multi_buffer = cx.new(|cx| {
22896        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22897        multi_buffer.push_excerpts(
22898            buffer_1.clone(),
22899            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22900            cx,
22901        );
22902        multi_buffer.push_excerpts(
22903            buffer_2.clone(),
22904            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22905            cx,
22906        );
22907        multi_buffer.push_excerpts(
22908            buffer_3.clone(),
22909            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22910            cx,
22911        );
22912        multi_buffer
22913    });
22914
22915    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22916        Editor::new(
22917            EditorMode::full(),
22918            multi_buffer,
22919            Some(project.clone()),
22920            window,
22921            cx,
22922        )
22923    });
22924
22925    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22926    assert_eq!(
22927        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22928        full_text,
22929    );
22930
22931    multi_buffer_editor.update(cx, |editor, cx| {
22932        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22933    });
22934    assert_eq!(
22935        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22936        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22937        "After folding the first buffer, its text should not be displayed"
22938    );
22939
22940    multi_buffer_editor.update(cx, |editor, cx| {
22941        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22942    });
22943
22944    assert_eq!(
22945        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22946        "\n\n\n\n\n\n7777\n8888\n9999",
22947        "After folding the second buffer, its text should not be displayed"
22948    );
22949
22950    multi_buffer_editor.update(cx, |editor, cx| {
22951        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22952    });
22953    assert_eq!(
22954        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22955        "\n\n\n\n\n",
22956        "After folding the third buffer, its text should not be displayed"
22957    );
22958
22959    multi_buffer_editor.update(cx, |editor, cx| {
22960        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22961    });
22962    assert_eq!(
22963        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22964        "\n\n\n\n4444\n5555\n6666\n\n",
22965        "After unfolding the second buffer, its text should be displayed"
22966    );
22967
22968    multi_buffer_editor.update(cx, |editor, cx| {
22969        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22970    });
22971    assert_eq!(
22972        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22973        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22974        "After unfolding the first buffer, its text should be displayed"
22975    );
22976
22977    multi_buffer_editor.update(cx, |editor, cx| {
22978        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22979    });
22980    assert_eq!(
22981        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22982        full_text,
22983        "After unfolding all buffers, all original text should be displayed"
22984    );
22985}
22986
22987#[gpui::test]
22988async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22989    init_test(cx, |_| {});
22990
22991    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22992
22993    let fs = FakeFs::new(cx.executor());
22994    fs.insert_tree(
22995        path!("/a"),
22996        json!({
22997            "main.rs": sample_text,
22998        }),
22999    )
23000    .await;
23001    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23002    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23003    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23004    let worktree = project.update(cx, |project, cx| {
23005        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23006        assert_eq!(worktrees.len(), 1);
23007        worktrees.pop().unwrap()
23008    });
23009    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23010
23011    let buffer_1 = project
23012        .update(cx, |project, cx| {
23013            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23014        })
23015        .await
23016        .unwrap();
23017
23018    let multi_buffer = cx.new(|cx| {
23019        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23020        multi_buffer.push_excerpts(
23021            buffer_1.clone(),
23022            [ExcerptRange::new(
23023                Point::new(0, 0)
23024                    ..Point::new(
23025                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23026                        0,
23027                    ),
23028            )],
23029            cx,
23030        );
23031        multi_buffer
23032    });
23033    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23034        Editor::new(
23035            EditorMode::full(),
23036            multi_buffer,
23037            Some(project.clone()),
23038            window,
23039            cx,
23040        )
23041    });
23042
23043    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23044    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23045        enum TestHighlight {}
23046        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23047        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23048        editor.highlight_text::<TestHighlight>(
23049            vec![highlight_range.clone()],
23050            HighlightStyle::color(Hsla::green()),
23051            cx,
23052        );
23053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23054            s.select_ranges(Some(highlight_range))
23055        });
23056    });
23057
23058    let full_text = format!("\n\n{sample_text}");
23059    assert_eq!(
23060        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23061        full_text,
23062    );
23063}
23064
23065#[gpui::test]
23066async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23067    init_test(cx, |_| {});
23068    cx.update(|cx| {
23069        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23070            "keymaps/default-linux.json",
23071            cx,
23072        )
23073        .unwrap();
23074        cx.bind_keys(default_key_bindings);
23075    });
23076
23077    let (editor, cx) = cx.add_window_view(|window, cx| {
23078        let multi_buffer = MultiBuffer::build_multi(
23079            [
23080                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23081                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23082                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23083                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23084            ],
23085            cx,
23086        );
23087        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23088
23089        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23090        // fold all but the second buffer, so that we test navigating between two
23091        // adjacent folded buffers, as well as folded buffers at the start and
23092        // end the multibuffer
23093        editor.fold_buffer(buffer_ids[0], cx);
23094        editor.fold_buffer(buffer_ids[2], cx);
23095        editor.fold_buffer(buffer_ids[3], cx);
23096
23097        editor
23098    });
23099    cx.simulate_resize(size(px(1000.), px(1000.)));
23100
23101    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23102    cx.assert_excerpts_with_selections(indoc! {"
23103        [EXCERPT]
23104        ˇ[FOLDED]
23105        [EXCERPT]
23106        a1
23107        b1
23108        [EXCERPT]
23109        [FOLDED]
23110        [EXCERPT]
23111        [FOLDED]
23112        "
23113    });
23114    cx.simulate_keystroke("down");
23115    cx.assert_excerpts_with_selections(indoc! {"
23116        [EXCERPT]
23117        [FOLDED]
23118        [EXCERPT]
23119        ˇa1
23120        b1
23121        [EXCERPT]
23122        [FOLDED]
23123        [EXCERPT]
23124        [FOLDED]
23125        "
23126    });
23127    cx.simulate_keystroke("down");
23128    cx.assert_excerpts_with_selections(indoc! {"
23129        [EXCERPT]
23130        [FOLDED]
23131        [EXCERPT]
23132        a1
23133        ˇb1
23134        [EXCERPT]
23135        [FOLDED]
23136        [EXCERPT]
23137        [FOLDED]
23138        "
23139    });
23140    cx.simulate_keystroke("down");
23141    cx.assert_excerpts_with_selections(indoc! {"
23142        [EXCERPT]
23143        [FOLDED]
23144        [EXCERPT]
23145        a1
23146        b1
23147        ˇ[EXCERPT]
23148        [FOLDED]
23149        [EXCERPT]
23150        [FOLDED]
23151        "
23152    });
23153    cx.simulate_keystroke("down");
23154    cx.assert_excerpts_with_selections(indoc! {"
23155        [EXCERPT]
23156        [FOLDED]
23157        [EXCERPT]
23158        a1
23159        b1
23160        [EXCERPT]
23161        ˇ[FOLDED]
23162        [EXCERPT]
23163        [FOLDED]
23164        "
23165    });
23166    for _ in 0..5 {
23167        cx.simulate_keystroke("down");
23168        cx.assert_excerpts_with_selections(indoc! {"
23169            [EXCERPT]
23170            [FOLDED]
23171            [EXCERPT]
23172            a1
23173            b1
23174            [EXCERPT]
23175            [FOLDED]
23176            [EXCERPT]
23177            ˇ[FOLDED]
23178            "
23179        });
23180    }
23181
23182    cx.simulate_keystroke("up");
23183    cx.assert_excerpts_with_selections(indoc! {"
23184        [EXCERPT]
23185        [FOLDED]
23186        [EXCERPT]
23187        a1
23188        b1
23189        [EXCERPT]
23190        ˇ[FOLDED]
23191        [EXCERPT]
23192        [FOLDED]
23193        "
23194    });
23195    cx.simulate_keystroke("up");
23196    cx.assert_excerpts_with_selections(indoc! {"
23197        [EXCERPT]
23198        [FOLDED]
23199        [EXCERPT]
23200        a1
23201        b1
23202        ˇ[EXCERPT]
23203        [FOLDED]
23204        [EXCERPT]
23205        [FOLDED]
23206        "
23207    });
23208    cx.simulate_keystroke("up");
23209    cx.assert_excerpts_with_selections(indoc! {"
23210        [EXCERPT]
23211        [FOLDED]
23212        [EXCERPT]
23213        a1
23214        ˇb1
23215        [EXCERPT]
23216        [FOLDED]
23217        [EXCERPT]
23218        [FOLDED]
23219        "
23220    });
23221    cx.simulate_keystroke("up");
23222    cx.assert_excerpts_with_selections(indoc! {"
23223        [EXCERPT]
23224        [FOLDED]
23225        [EXCERPT]
23226        ˇa1
23227        b1
23228        [EXCERPT]
23229        [FOLDED]
23230        [EXCERPT]
23231        [FOLDED]
23232        "
23233    });
23234    for _ in 0..5 {
23235        cx.simulate_keystroke("up");
23236        cx.assert_excerpts_with_selections(indoc! {"
23237            [EXCERPT]
23238            ˇ[FOLDED]
23239            [EXCERPT]
23240            a1
23241            b1
23242            [EXCERPT]
23243            [FOLDED]
23244            [EXCERPT]
23245            [FOLDED]
23246            "
23247        });
23248    }
23249}
23250
23251#[gpui::test]
23252async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23253    init_test(cx, |_| {});
23254
23255    // Simple insertion
23256    assert_highlighted_edits(
23257        "Hello, world!",
23258        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23259        true,
23260        cx,
23261        |highlighted_edits, cx| {
23262            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23263            assert_eq!(highlighted_edits.highlights.len(), 1);
23264            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23265            assert_eq!(
23266                highlighted_edits.highlights[0].1.background_color,
23267                Some(cx.theme().status().created_background)
23268            );
23269        },
23270    )
23271    .await;
23272
23273    // Replacement
23274    assert_highlighted_edits(
23275        "This is a test.",
23276        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23277        false,
23278        cx,
23279        |highlighted_edits, cx| {
23280            assert_eq!(highlighted_edits.text, "That is a test.");
23281            assert_eq!(highlighted_edits.highlights.len(), 1);
23282            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23283            assert_eq!(
23284                highlighted_edits.highlights[0].1.background_color,
23285                Some(cx.theme().status().created_background)
23286            );
23287        },
23288    )
23289    .await;
23290
23291    // Multiple edits
23292    assert_highlighted_edits(
23293        "Hello, world!",
23294        vec![
23295            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23296            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23297        ],
23298        false,
23299        cx,
23300        |highlighted_edits, cx| {
23301            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23302            assert_eq!(highlighted_edits.highlights.len(), 2);
23303            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23304            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23305            assert_eq!(
23306                highlighted_edits.highlights[0].1.background_color,
23307                Some(cx.theme().status().created_background)
23308            );
23309            assert_eq!(
23310                highlighted_edits.highlights[1].1.background_color,
23311                Some(cx.theme().status().created_background)
23312            );
23313        },
23314    )
23315    .await;
23316
23317    // Multiple lines with edits
23318    assert_highlighted_edits(
23319        "First line\nSecond line\nThird line\nFourth line",
23320        vec![
23321            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23322            (
23323                Point::new(2, 0)..Point::new(2, 10),
23324                "New third line".to_string(),
23325            ),
23326            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23327        ],
23328        false,
23329        cx,
23330        |highlighted_edits, cx| {
23331            assert_eq!(
23332                highlighted_edits.text,
23333                "Second modified\nNew third line\nFourth updated line"
23334            );
23335            assert_eq!(highlighted_edits.highlights.len(), 3);
23336            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23337            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23338            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23339            for highlight in &highlighted_edits.highlights {
23340                assert_eq!(
23341                    highlight.1.background_color,
23342                    Some(cx.theme().status().created_background)
23343                );
23344            }
23345        },
23346    )
23347    .await;
23348}
23349
23350#[gpui::test]
23351async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23352    init_test(cx, |_| {});
23353
23354    // Deletion
23355    assert_highlighted_edits(
23356        "Hello, world!",
23357        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23358        true,
23359        cx,
23360        |highlighted_edits, cx| {
23361            assert_eq!(highlighted_edits.text, "Hello, world!");
23362            assert_eq!(highlighted_edits.highlights.len(), 1);
23363            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23364            assert_eq!(
23365                highlighted_edits.highlights[0].1.background_color,
23366                Some(cx.theme().status().deleted_background)
23367            );
23368        },
23369    )
23370    .await;
23371
23372    // Insertion
23373    assert_highlighted_edits(
23374        "Hello, world!",
23375        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23376        true,
23377        cx,
23378        |highlighted_edits, cx| {
23379            assert_eq!(highlighted_edits.highlights.len(), 1);
23380            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23381            assert_eq!(
23382                highlighted_edits.highlights[0].1.background_color,
23383                Some(cx.theme().status().created_background)
23384            );
23385        },
23386    )
23387    .await;
23388}
23389
23390async fn assert_highlighted_edits(
23391    text: &str,
23392    edits: Vec<(Range<Point>, String)>,
23393    include_deletions: bool,
23394    cx: &mut TestAppContext,
23395    assertion_fn: impl Fn(HighlightedText, &App),
23396) {
23397    let window = cx.add_window(|window, cx| {
23398        let buffer = MultiBuffer::build_simple(text, cx);
23399        Editor::new(EditorMode::full(), buffer, None, window, cx)
23400    });
23401    let cx = &mut VisualTestContext::from_window(*window, cx);
23402
23403    let (buffer, snapshot) = window
23404        .update(cx, |editor, _window, cx| {
23405            (
23406                editor.buffer().clone(),
23407                editor.buffer().read(cx).snapshot(cx),
23408            )
23409        })
23410        .unwrap();
23411
23412    let edits = edits
23413        .into_iter()
23414        .map(|(range, edit)| {
23415            (
23416                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23417                edit,
23418            )
23419        })
23420        .collect::<Vec<_>>();
23421
23422    let text_anchor_edits = edits
23423        .clone()
23424        .into_iter()
23425        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23426        .collect::<Vec<_>>();
23427
23428    let edit_preview = window
23429        .update(cx, |_, _window, cx| {
23430            buffer
23431                .read(cx)
23432                .as_singleton()
23433                .unwrap()
23434                .read(cx)
23435                .preview_edits(text_anchor_edits.into(), cx)
23436        })
23437        .unwrap()
23438        .await;
23439
23440    cx.update(|_window, cx| {
23441        let highlighted_edits = edit_prediction_edit_text(
23442            snapshot.as_singleton().unwrap().2,
23443            &edits,
23444            &edit_preview,
23445            include_deletions,
23446            cx,
23447        );
23448        assertion_fn(highlighted_edits, cx)
23449    });
23450}
23451
23452#[track_caller]
23453fn assert_breakpoint(
23454    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23455    path: &Arc<Path>,
23456    expected: Vec<(u32, Breakpoint)>,
23457) {
23458    if expected.is_empty() {
23459        assert!(!breakpoints.contains_key(path), "{}", path.display());
23460    } else {
23461        let mut breakpoint = breakpoints
23462            .get(path)
23463            .unwrap()
23464            .iter()
23465            .map(|breakpoint| {
23466                (
23467                    breakpoint.row,
23468                    Breakpoint {
23469                        message: breakpoint.message.clone(),
23470                        state: breakpoint.state,
23471                        condition: breakpoint.condition.clone(),
23472                        hit_condition: breakpoint.hit_condition.clone(),
23473                    },
23474                )
23475            })
23476            .collect::<Vec<_>>();
23477
23478        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23479
23480        assert_eq!(expected, breakpoint);
23481    }
23482}
23483
23484fn add_log_breakpoint_at_cursor(
23485    editor: &mut Editor,
23486    log_message: &str,
23487    window: &mut Window,
23488    cx: &mut Context<Editor>,
23489) {
23490    let (anchor, bp) = editor
23491        .breakpoints_at_cursors(window, cx)
23492        .first()
23493        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23494        .unwrap_or_else(|| {
23495            let snapshot = editor.snapshot(window, cx);
23496            let cursor_position: Point =
23497                editor.selections.newest(&snapshot.display_snapshot).head();
23498
23499            let breakpoint_position = snapshot
23500                .buffer_snapshot()
23501                .anchor_before(Point::new(cursor_position.row, 0));
23502
23503            (breakpoint_position, Breakpoint::new_log(log_message))
23504        });
23505
23506    editor.edit_breakpoint_at_anchor(
23507        anchor,
23508        bp,
23509        BreakpointEditAction::EditLogMessage(log_message.into()),
23510        cx,
23511    );
23512}
23513
23514#[gpui::test]
23515async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23516    init_test(cx, |_| {});
23517
23518    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23519    let fs = FakeFs::new(cx.executor());
23520    fs.insert_tree(
23521        path!("/a"),
23522        json!({
23523            "main.rs": sample_text,
23524        }),
23525    )
23526    .await;
23527    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23528    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23529    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23530
23531    let fs = FakeFs::new(cx.executor());
23532    fs.insert_tree(
23533        path!("/a"),
23534        json!({
23535            "main.rs": sample_text,
23536        }),
23537    )
23538    .await;
23539    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23540    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23541    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23542    let worktree_id = workspace
23543        .update(cx, |workspace, _window, cx| {
23544            workspace.project().update(cx, |project, cx| {
23545                project.worktrees(cx).next().unwrap().read(cx).id()
23546            })
23547        })
23548        .unwrap();
23549
23550    let buffer = project
23551        .update(cx, |project, cx| {
23552            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23553        })
23554        .await
23555        .unwrap();
23556
23557    let (editor, cx) = cx.add_window_view(|window, cx| {
23558        Editor::new(
23559            EditorMode::full(),
23560            MultiBuffer::build_from_buffer(buffer, cx),
23561            Some(project.clone()),
23562            window,
23563            cx,
23564        )
23565    });
23566
23567    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23568    let abs_path = project.read_with(cx, |project, cx| {
23569        project
23570            .absolute_path(&project_path, cx)
23571            .map(Arc::from)
23572            .unwrap()
23573    });
23574
23575    // assert we can add breakpoint on the first line
23576    editor.update_in(cx, |editor, window, cx| {
23577        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23578        editor.move_to_end(&MoveToEnd, window, cx);
23579        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23580    });
23581
23582    let breakpoints = editor.update(cx, |editor, cx| {
23583        editor
23584            .breakpoint_store()
23585            .as_ref()
23586            .unwrap()
23587            .read(cx)
23588            .all_source_breakpoints(cx)
23589    });
23590
23591    assert_eq!(1, breakpoints.len());
23592    assert_breakpoint(
23593        &breakpoints,
23594        &abs_path,
23595        vec![
23596            (0, Breakpoint::new_standard()),
23597            (3, Breakpoint::new_standard()),
23598        ],
23599    );
23600
23601    editor.update_in(cx, |editor, window, cx| {
23602        editor.move_to_beginning(&MoveToBeginning, window, cx);
23603        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23604    });
23605
23606    let breakpoints = editor.update(cx, |editor, cx| {
23607        editor
23608            .breakpoint_store()
23609            .as_ref()
23610            .unwrap()
23611            .read(cx)
23612            .all_source_breakpoints(cx)
23613    });
23614
23615    assert_eq!(1, breakpoints.len());
23616    assert_breakpoint(
23617        &breakpoints,
23618        &abs_path,
23619        vec![(3, Breakpoint::new_standard())],
23620    );
23621
23622    editor.update_in(cx, |editor, window, cx| {
23623        editor.move_to_end(&MoveToEnd, window, cx);
23624        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23625    });
23626
23627    let breakpoints = editor.update(cx, |editor, cx| {
23628        editor
23629            .breakpoint_store()
23630            .as_ref()
23631            .unwrap()
23632            .read(cx)
23633            .all_source_breakpoints(cx)
23634    });
23635
23636    assert_eq!(0, breakpoints.len());
23637    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23638}
23639
23640#[gpui::test]
23641async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23642    init_test(cx, |_| {});
23643
23644    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23645
23646    let fs = FakeFs::new(cx.executor());
23647    fs.insert_tree(
23648        path!("/a"),
23649        json!({
23650            "main.rs": sample_text,
23651        }),
23652    )
23653    .await;
23654    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23655    let (workspace, cx) =
23656        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23657
23658    let worktree_id = workspace.update(cx, |workspace, cx| {
23659        workspace.project().update(cx, |project, cx| {
23660            project.worktrees(cx).next().unwrap().read(cx).id()
23661        })
23662    });
23663
23664    let buffer = project
23665        .update(cx, |project, cx| {
23666            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23667        })
23668        .await
23669        .unwrap();
23670
23671    let (editor, cx) = cx.add_window_view(|window, cx| {
23672        Editor::new(
23673            EditorMode::full(),
23674            MultiBuffer::build_from_buffer(buffer, cx),
23675            Some(project.clone()),
23676            window,
23677            cx,
23678        )
23679    });
23680
23681    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23682    let abs_path = project.read_with(cx, |project, cx| {
23683        project
23684            .absolute_path(&project_path, cx)
23685            .map(Arc::from)
23686            .unwrap()
23687    });
23688
23689    editor.update_in(cx, |editor, window, cx| {
23690        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23691    });
23692
23693    let breakpoints = editor.update(cx, |editor, cx| {
23694        editor
23695            .breakpoint_store()
23696            .as_ref()
23697            .unwrap()
23698            .read(cx)
23699            .all_source_breakpoints(cx)
23700    });
23701
23702    assert_breakpoint(
23703        &breakpoints,
23704        &abs_path,
23705        vec![(0, Breakpoint::new_log("hello world"))],
23706    );
23707
23708    // Removing a log message from a log breakpoint should remove it
23709    editor.update_in(cx, |editor, window, cx| {
23710        add_log_breakpoint_at_cursor(editor, "", window, cx);
23711    });
23712
23713    let breakpoints = editor.update(cx, |editor, cx| {
23714        editor
23715            .breakpoint_store()
23716            .as_ref()
23717            .unwrap()
23718            .read(cx)
23719            .all_source_breakpoints(cx)
23720    });
23721
23722    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23723
23724    editor.update_in(cx, |editor, window, cx| {
23725        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23726        editor.move_to_end(&MoveToEnd, window, cx);
23727        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23728        // Not adding a log message to a standard breakpoint shouldn't remove it
23729        add_log_breakpoint_at_cursor(editor, "", window, cx);
23730    });
23731
23732    let breakpoints = editor.update(cx, |editor, cx| {
23733        editor
23734            .breakpoint_store()
23735            .as_ref()
23736            .unwrap()
23737            .read(cx)
23738            .all_source_breakpoints(cx)
23739    });
23740
23741    assert_breakpoint(
23742        &breakpoints,
23743        &abs_path,
23744        vec![
23745            (0, Breakpoint::new_standard()),
23746            (3, Breakpoint::new_standard()),
23747        ],
23748    );
23749
23750    editor.update_in(cx, |editor, window, cx| {
23751        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23752    });
23753
23754    let breakpoints = editor.update(cx, |editor, cx| {
23755        editor
23756            .breakpoint_store()
23757            .as_ref()
23758            .unwrap()
23759            .read(cx)
23760            .all_source_breakpoints(cx)
23761    });
23762
23763    assert_breakpoint(
23764        &breakpoints,
23765        &abs_path,
23766        vec![
23767            (0, Breakpoint::new_standard()),
23768            (3, Breakpoint::new_log("hello world")),
23769        ],
23770    );
23771
23772    editor.update_in(cx, |editor, window, cx| {
23773        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23774    });
23775
23776    let breakpoints = editor.update(cx, |editor, cx| {
23777        editor
23778            .breakpoint_store()
23779            .as_ref()
23780            .unwrap()
23781            .read(cx)
23782            .all_source_breakpoints(cx)
23783    });
23784
23785    assert_breakpoint(
23786        &breakpoints,
23787        &abs_path,
23788        vec![
23789            (0, Breakpoint::new_standard()),
23790            (3, Breakpoint::new_log("hello Earth!!")),
23791        ],
23792    );
23793}
23794
23795/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23796/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23797/// or when breakpoints were placed out of order. This tests for a regression too
23798#[gpui::test]
23799async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23800    init_test(cx, |_| {});
23801
23802    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23803    let fs = FakeFs::new(cx.executor());
23804    fs.insert_tree(
23805        path!("/a"),
23806        json!({
23807            "main.rs": sample_text,
23808        }),
23809    )
23810    .await;
23811    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23812    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23813    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23814
23815    let fs = FakeFs::new(cx.executor());
23816    fs.insert_tree(
23817        path!("/a"),
23818        json!({
23819            "main.rs": sample_text,
23820        }),
23821    )
23822    .await;
23823    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23824    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23825    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23826    let worktree_id = workspace
23827        .update(cx, |workspace, _window, cx| {
23828            workspace.project().update(cx, |project, cx| {
23829                project.worktrees(cx).next().unwrap().read(cx).id()
23830            })
23831        })
23832        .unwrap();
23833
23834    let buffer = project
23835        .update(cx, |project, cx| {
23836            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23837        })
23838        .await
23839        .unwrap();
23840
23841    let (editor, cx) = cx.add_window_view(|window, cx| {
23842        Editor::new(
23843            EditorMode::full(),
23844            MultiBuffer::build_from_buffer(buffer, cx),
23845            Some(project.clone()),
23846            window,
23847            cx,
23848        )
23849    });
23850
23851    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23852    let abs_path = project.read_with(cx, |project, cx| {
23853        project
23854            .absolute_path(&project_path, cx)
23855            .map(Arc::from)
23856            .unwrap()
23857    });
23858
23859    // assert we can add breakpoint on the first line
23860    editor.update_in(cx, |editor, window, cx| {
23861        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23862        editor.move_to_end(&MoveToEnd, window, cx);
23863        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23864        editor.move_up(&MoveUp, window, cx);
23865        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23866    });
23867
23868    let breakpoints = editor.update(cx, |editor, cx| {
23869        editor
23870            .breakpoint_store()
23871            .as_ref()
23872            .unwrap()
23873            .read(cx)
23874            .all_source_breakpoints(cx)
23875    });
23876
23877    assert_eq!(1, breakpoints.len());
23878    assert_breakpoint(
23879        &breakpoints,
23880        &abs_path,
23881        vec![
23882            (0, Breakpoint::new_standard()),
23883            (2, Breakpoint::new_standard()),
23884            (3, Breakpoint::new_standard()),
23885        ],
23886    );
23887
23888    editor.update_in(cx, |editor, window, cx| {
23889        editor.move_to_beginning(&MoveToBeginning, window, cx);
23890        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23891        editor.move_to_end(&MoveToEnd, window, cx);
23892        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23893        // Disabling a breakpoint that doesn't exist should do nothing
23894        editor.move_up(&MoveUp, window, cx);
23895        editor.move_up(&MoveUp, window, cx);
23896        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23897    });
23898
23899    let breakpoints = editor.update(cx, |editor, cx| {
23900        editor
23901            .breakpoint_store()
23902            .as_ref()
23903            .unwrap()
23904            .read(cx)
23905            .all_source_breakpoints(cx)
23906    });
23907
23908    let disable_breakpoint = {
23909        let mut bp = Breakpoint::new_standard();
23910        bp.state = BreakpointState::Disabled;
23911        bp
23912    };
23913
23914    assert_eq!(1, breakpoints.len());
23915    assert_breakpoint(
23916        &breakpoints,
23917        &abs_path,
23918        vec![
23919            (0, disable_breakpoint.clone()),
23920            (2, Breakpoint::new_standard()),
23921            (3, disable_breakpoint.clone()),
23922        ],
23923    );
23924
23925    editor.update_in(cx, |editor, window, cx| {
23926        editor.move_to_beginning(&MoveToBeginning, window, cx);
23927        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23928        editor.move_to_end(&MoveToEnd, window, cx);
23929        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23930        editor.move_up(&MoveUp, window, cx);
23931        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23932    });
23933
23934    let breakpoints = editor.update(cx, |editor, cx| {
23935        editor
23936            .breakpoint_store()
23937            .as_ref()
23938            .unwrap()
23939            .read(cx)
23940            .all_source_breakpoints(cx)
23941    });
23942
23943    assert_eq!(1, breakpoints.len());
23944    assert_breakpoint(
23945        &breakpoints,
23946        &abs_path,
23947        vec![
23948            (0, Breakpoint::new_standard()),
23949            (2, disable_breakpoint),
23950            (3, Breakpoint::new_standard()),
23951        ],
23952    );
23953}
23954
23955#[gpui::test]
23956async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23957    init_test(cx, |_| {});
23958    let capabilities = lsp::ServerCapabilities {
23959        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23960            prepare_provider: Some(true),
23961            work_done_progress_options: Default::default(),
23962        })),
23963        ..Default::default()
23964    };
23965    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23966
23967    cx.set_state(indoc! {"
23968        struct Fˇoo {}
23969    "});
23970
23971    cx.update_editor(|editor, _, cx| {
23972        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23973        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23974        editor.highlight_background::<DocumentHighlightRead>(
23975            &[highlight_range],
23976            |theme| theme.colors().editor_document_highlight_read_background,
23977            cx,
23978        );
23979    });
23980
23981    let mut prepare_rename_handler = cx
23982        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23983            move |_, _, _| async move {
23984                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23985                    start: lsp::Position {
23986                        line: 0,
23987                        character: 7,
23988                    },
23989                    end: lsp::Position {
23990                        line: 0,
23991                        character: 10,
23992                    },
23993                })))
23994            },
23995        );
23996    let prepare_rename_task = cx
23997        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23998        .expect("Prepare rename was not started");
23999    prepare_rename_handler.next().await.unwrap();
24000    prepare_rename_task.await.expect("Prepare rename failed");
24001
24002    let mut rename_handler =
24003        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24004            let edit = lsp::TextEdit {
24005                range: lsp::Range {
24006                    start: lsp::Position {
24007                        line: 0,
24008                        character: 7,
24009                    },
24010                    end: lsp::Position {
24011                        line: 0,
24012                        character: 10,
24013                    },
24014                },
24015                new_text: "FooRenamed".to_string(),
24016            };
24017            Ok(Some(lsp::WorkspaceEdit::new(
24018                // Specify the same edit twice
24019                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24020            )))
24021        });
24022    let rename_task = cx
24023        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24024        .expect("Confirm rename was not started");
24025    rename_handler.next().await.unwrap();
24026    rename_task.await.expect("Confirm rename failed");
24027    cx.run_until_parked();
24028
24029    // Despite two edits, only one is actually applied as those are identical
24030    cx.assert_editor_state(indoc! {"
24031        struct FooRenamedˇ {}
24032    "});
24033}
24034
24035#[gpui::test]
24036async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24037    init_test(cx, |_| {});
24038    // These capabilities indicate that the server does not support prepare rename.
24039    let capabilities = lsp::ServerCapabilities {
24040        rename_provider: Some(lsp::OneOf::Left(true)),
24041        ..Default::default()
24042    };
24043    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24044
24045    cx.set_state(indoc! {"
24046        struct Fˇoo {}
24047    "});
24048
24049    cx.update_editor(|editor, _window, cx| {
24050        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24051        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24052        editor.highlight_background::<DocumentHighlightRead>(
24053            &[highlight_range],
24054            |theme| theme.colors().editor_document_highlight_read_background,
24055            cx,
24056        );
24057    });
24058
24059    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24060        .expect("Prepare rename was not started")
24061        .await
24062        .expect("Prepare rename failed");
24063
24064    let mut rename_handler =
24065        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24066            let edit = lsp::TextEdit {
24067                range: lsp::Range {
24068                    start: lsp::Position {
24069                        line: 0,
24070                        character: 7,
24071                    },
24072                    end: lsp::Position {
24073                        line: 0,
24074                        character: 10,
24075                    },
24076                },
24077                new_text: "FooRenamed".to_string(),
24078            };
24079            Ok(Some(lsp::WorkspaceEdit::new(
24080                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24081            )))
24082        });
24083    let rename_task = cx
24084        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24085        .expect("Confirm rename was not started");
24086    rename_handler.next().await.unwrap();
24087    rename_task.await.expect("Confirm rename failed");
24088    cx.run_until_parked();
24089
24090    // Correct range is renamed, as `surrounding_word` is used to find it.
24091    cx.assert_editor_state(indoc! {"
24092        struct FooRenamedˇ {}
24093    "});
24094}
24095
24096#[gpui::test]
24097async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24098    init_test(cx, |_| {});
24099    let mut cx = EditorTestContext::new(cx).await;
24100
24101    let language = Arc::new(
24102        Language::new(
24103            LanguageConfig::default(),
24104            Some(tree_sitter_html::LANGUAGE.into()),
24105        )
24106        .with_brackets_query(
24107            r#"
24108            ("<" @open "/>" @close)
24109            ("</" @open ">" @close)
24110            ("<" @open ">" @close)
24111            ("\"" @open "\"" @close)
24112            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24113        "#,
24114        )
24115        .unwrap(),
24116    );
24117    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24118
24119    cx.set_state(indoc! {"
24120        <span>ˇ</span>
24121    "});
24122    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24123    cx.assert_editor_state(indoc! {"
24124        <span>
24125        ˇ
24126        </span>
24127    "});
24128
24129    cx.set_state(indoc! {"
24130        <span><span></span>ˇ</span>
24131    "});
24132    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24133    cx.assert_editor_state(indoc! {"
24134        <span><span></span>
24135        ˇ</span>
24136    "});
24137
24138    cx.set_state(indoc! {"
24139        <span>ˇ
24140        </span>
24141    "});
24142    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24143    cx.assert_editor_state(indoc! {"
24144        <span>
24145        ˇ
24146        </span>
24147    "});
24148}
24149
24150#[gpui::test(iterations = 10)]
24151async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24152    init_test(cx, |_| {});
24153
24154    let fs = FakeFs::new(cx.executor());
24155    fs.insert_tree(
24156        path!("/dir"),
24157        json!({
24158            "a.ts": "a",
24159        }),
24160    )
24161    .await;
24162
24163    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24164    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24165    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24166
24167    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24168    language_registry.add(Arc::new(Language::new(
24169        LanguageConfig {
24170            name: "TypeScript".into(),
24171            matcher: LanguageMatcher {
24172                path_suffixes: vec!["ts".to_string()],
24173                ..Default::default()
24174            },
24175            ..Default::default()
24176        },
24177        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24178    )));
24179    let mut fake_language_servers = language_registry.register_fake_lsp(
24180        "TypeScript",
24181        FakeLspAdapter {
24182            capabilities: lsp::ServerCapabilities {
24183                code_lens_provider: Some(lsp::CodeLensOptions {
24184                    resolve_provider: Some(true),
24185                }),
24186                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24187                    commands: vec!["_the/command".to_string()],
24188                    ..lsp::ExecuteCommandOptions::default()
24189                }),
24190                ..lsp::ServerCapabilities::default()
24191            },
24192            ..FakeLspAdapter::default()
24193        },
24194    );
24195
24196    let editor = workspace
24197        .update(cx, |workspace, window, cx| {
24198            workspace.open_abs_path(
24199                PathBuf::from(path!("/dir/a.ts")),
24200                OpenOptions::default(),
24201                window,
24202                cx,
24203            )
24204        })
24205        .unwrap()
24206        .await
24207        .unwrap()
24208        .downcast::<Editor>()
24209        .unwrap();
24210    cx.executor().run_until_parked();
24211
24212    let fake_server = fake_language_servers.next().await.unwrap();
24213
24214    let buffer = editor.update(cx, |editor, cx| {
24215        editor
24216            .buffer()
24217            .read(cx)
24218            .as_singleton()
24219            .expect("have opened a single file by path")
24220    });
24221
24222    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24223    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24224    drop(buffer_snapshot);
24225    let actions = cx
24226        .update_window(*workspace, |_, window, cx| {
24227            project.code_actions(&buffer, anchor..anchor, window, cx)
24228        })
24229        .unwrap();
24230
24231    fake_server
24232        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24233            Ok(Some(vec![
24234                lsp::CodeLens {
24235                    range: lsp::Range::default(),
24236                    command: Some(lsp::Command {
24237                        title: "Code lens command".to_owned(),
24238                        command: "_the/command".to_owned(),
24239                        arguments: None,
24240                    }),
24241                    data: None,
24242                },
24243                lsp::CodeLens {
24244                    range: lsp::Range::default(),
24245                    command: Some(lsp::Command {
24246                        title: "Command not in capabilities".to_owned(),
24247                        command: "not in capabilities".to_owned(),
24248                        arguments: None,
24249                    }),
24250                    data: None,
24251                },
24252                lsp::CodeLens {
24253                    range: lsp::Range {
24254                        start: lsp::Position {
24255                            line: 1,
24256                            character: 1,
24257                        },
24258                        end: lsp::Position {
24259                            line: 1,
24260                            character: 1,
24261                        },
24262                    },
24263                    command: Some(lsp::Command {
24264                        title: "Command not in range".to_owned(),
24265                        command: "_the/command".to_owned(),
24266                        arguments: None,
24267                    }),
24268                    data: None,
24269                },
24270            ]))
24271        })
24272        .next()
24273        .await;
24274
24275    let actions = actions.await.unwrap();
24276    assert_eq!(
24277        actions.len(),
24278        1,
24279        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24280    );
24281    let action = actions[0].clone();
24282    let apply = project.update(cx, |project, cx| {
24283        project.apply_code_action(buffer.clone(), action, true, cx)
24284    });
24285
24286    // Resolving the code action does not populate its edits. In absence of
24287    // edits, we must execute the given command.
24288    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24289        |mut lens, _| async move {
24290            let lens_command = lens.command.as_mut().expect("should have a command");
24291            assert_eq!(lens_command.title, "Code lens command");
24292            lens_command.arguments = Some(vec![json!("the-argument")]);
24293            Ok(lens)
24294        },
24295    );
24296
24297    // While executing the command, the language server sends the editor
24298    // a `workspaceEdit` request.
24299    fake_server
24300        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24301            let fake = fake_server.clone();
24302            move |params, _| {
24303                assert_eq!(params.command, "_the/command");
24304                let fake = fake.clone();
24305                async move {
24306                    fake.server
24307                        .request::<lsp::request::ApplyWorkspaceEdit>(
24308                            lsp::ApplyWorkspaceEditParams {
24309                                label: None,
24310                                edit: lsp::WorkspaceEdit {
24311                                    changes: Some(
24312                                        [(
24313                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24314                                            vec![lsp::TextEdit {
24315                                                range: lsp::Range::new(
24316                                                    lsp::Position::new(0, 0),
24317                                                    lsp::Position::new(0, 0),
24318                                                ),
24319                                                new_text: "X".into(),
24320                                            }],
24321                                        )]
24322                                        .into_iter()
24323                                        .collect(),
24324                                    ),
24325                                    ..lsp::WorkspaceEdit::default()
24326                                },
24327                            },
24328                        )
24329                        .await
24330                        .into_response()
24331                        .unwrap();
24332                    Ok(Some(json!(null)))
24333                }
24334            }
24335        })
24336        .next()
24337        .await;
24338
24339    // Applying the code lens command returns a project transaction containing the edits
24340    // sent by the language server in its `workspaceEdit` request.
24341    let transaction = apply.await.unwrap();
24342    assert!(transaction.0.contains_key(&buffer));
24343    buffer.update(cx, |buffer, cx| {
24344        assert_eq!(buffer.text(), "Xa");
24345        buffer.undo(cx);
24346        assert_eq!(buffer.text(), "a");
24347    });
24348
24349    let actions_after_edits = cx
24350        .update_window(*workspace, |_, window, cx| {
24351            project.code_actions(&buffer, anchor..anchor, window, cx)
24352        })
24353        .unwrap()
24354        .await
24355        .unwrap();
24356    assert_eq!(
24357        actions, actions_after_edits,
24358        "For the same selection, same code lens actions should be returned"
24359    );
24360
24361    let _responses =
24362        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24363            panic!("No more code lens requests are expected");
24364        });
24365    editor.update_in(cx, |editor, window, cx| {
24366        editor.select_all(&SelectAll, window, cx);
24367    });
24368    cx.executor().run_until_parked();
24369    let new_actions = cx
24370        .update_window(*workspace, |_, window, cx| {
24371            project.code_actions(&buffer, anchor..anchor, window, cx)
24372        })
24373        .unwrap()
24374        .await
24375        .unwrap();
24376    assert_eq!(
24377        actions, new_actions,
24378        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24379    );
24380}
24381
24382#[gpui::test]
24383async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24384    init_test(cx, |_| {});
24385
24386    let fs = FakeFs::new(cx.executor());
24387    let main_text = r#"fn main() {
24388println!("1");
24389println!("2");
24390println!("3");
24391println!("4");
24392println!("5");
24393}"#;
24394    let lib_text = "mod foo {}";
24395    fs.insert_tree(
24396        path!("/a"),
24397        json!({
24398            "lib.rs": lib_text,
24399            "main.rs": main_text,
24400        }),
24401    )
24402    .await;
24403
24404    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24405    let (workspace, cx) =
24406        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24407    let worktree_id = workspace.update(cx, |workspace, cx| {
24408        workspace.project().update(cx, |project, cx| {
24409            project.worktrees(cx).next().unwrap().read(cx).id()
24410        })
24411    });
24412
24413    let expected_ranges = vec![
24414        Point::new(0, 0)..Point::new(0, 0),
24415        Point::new(1, 0)..Point::new(1, 1),
24416        Point::new(2, 0)..Point::new(2, 2),
24417        Point::new(3, 0)..Point::new(3, 3),
24418    ];
24419
24420    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24421    let editor_1 = workspace
24422        .update_in(cx, |workspace, window, cx| {
24423            workspace.open_path(
24424                (worktree_id, rel_path("main.rs")),
24425                Some(pane_1.downgrade()),
24426                true,
24427                window,
24428                cx,
24429            )
24430        })
24431        .unwrap()
24432        .await
24433        .downcast::<Editor>()
24434        .unwrap();
24435    pane_1.update(cx, |pane, cx| {
24436        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24437        open_editor.update(cx, |editor, cx| {
24438            assert_eq!(
24439                editor.display_text(cx),
24440                main_text,
24441                "Original main.rs text on initial open",
24442            );
24443            assert_eq!(
24444                editor
24445                    .selections
24446                    .all::<Point>(&editor.display_snapshot(cx))
24447                    .into_iter()
24448                    .map(|s| s.range())
24449                    .collect::<Vec<_>>(),
24450                vec![Point::zero()..Point::zero()],
24451                "Default selections on initial open",
24452            );
24453        })
24454    });
24455    editor_1.update_in(cx, |editor, window, cx| {
24456        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24457            s.select_ranges(expected_ranges.clone());
24458        });
24459    });
24460
24461    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24462        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24463    });
24464    let editor_2 = workspace
24465        .update_in(cx, |workspace, window, cx| {
24466            workspace.open_path(
24467                (worktree_id, rel_path("main.rs")),
24468                Some(pane_2.downgrade()),
24469                true,
24470                window,
24471                cx,
24472            )
24473        })
24474        .unwrap()
24475        .await
24476        .downcast::<Editor>()
24477        .unwrap();
24478    pane_2.update(cx, |pane, cx| {
24479        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24480        open_editor.update(cx, |editor, cx| {
24481            assert_eq!(
24482                editor.display_text(cx),
24483                main_text,
24484                "Original main.rs text on initial open in another panel",
24485            );
24486            assert_eq!(
24487                editor
24488                    .selections
24489                    .all::<Point>(&editor.display_snapshot(cx))
24490                    .into_iter()
24491                    .map(|s| s.range())
24492                    .collect::<Vec<_>>(),
24493                vec![Point::zero()..Point::zero()],
24494                "Default selections on initial open in another panel",
24495            );
24496        })
24497    });
24498
24499    editor_2.update_in(cx, |editor, window, cx| {
24500        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24501    });
24502
24503    let _other_editor_1 = workspace
24504        .update_in(cx, |workspace, window, cx| {
24505            workspace.open_path(
24506                (worktree_id, rel_path("lib.rs")),
24507                Some(pane_1.downgrade()),
24508                true,
24509                window,
24510                cx,
24511            )
24512        })
24513        .unwrap()
24514        .await
24515        .downcast::<Editor>()
24516        .unwrap();
24517    pane_1
24518        .update_in(cx, |pane, window, cx| {
24519            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24520        })
24521        .await
24522        .unwrap();
24523    drop(editor_1);
24524    pane_1.update(cx, |pane, cx| {
24525        pane.active_item()
24526            .unwrap()
24527            .downcast::<Editor>()
24528            .unwrap()
24529            .update(cx, |editor, cx| {
24530                assert_eq!(
24531                    editor.display_text(cx),
24532                    lib_text,
24533                    "Other file should be open and active",
24534                );
24535            });
24536        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24537    });
24538
24539    let _other_editor_2 = workspace
24540        .update_in(cx, |workspace, window, cx| {
24541            workspace.open_path(
24542                (worktree_id, rel_path("lib.rs")),
24543                Some(pane_2.downgrade()),
24544                true,
24545                window,
24546                cx,
24547            )
24548        })
24549        .unwrap()
24550        .await
24551        .downcast::<Editor>()
24552        .unwrap();
24553    pane_2
24554        .update_in(cx, |pane, window, cx| {
24555            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24556        })
24557        .await
24558        .unwrap();
24559    drop(editor_2);
24560    pane_2.update(cx, |pane, cx| {
24561        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24562        open_editor.update(cx, |editor, cx| {
24563            assert_eq!(
24564                editor.display_text(cx),
24565                lib_text,
24566                "Other file should be open and active in another panel too",
24567            );
24568        });
24569        assert_eq!(
24570            pane.items().count(),
24571            1,
24572            "No other editors should be open in another pane",
24573        );
24574    });
24575
24576    let _editor_1_reopened = workspace
24577        .update_in(cx, |workspace, window, cx| {
24578            workspace.open_path(
24579                (worktree_id, rel_path("main.rs")),
24580                Some(pane_1.downgrade()),
24581                true,
24582                window,
24583                cx,
24584            )
24585        })
24586        .unwrap()
24587        .await
24588        .downcast::<Editor>()
24589        .unwrap();
24590    let _editor_2_reopened = workspace
24591        .update_in(cx, |workspace, window, cx| {
24592            workspace.open_path(
24593                (worktree_id, rel_path("main.rs")),
24594                Some(pane_2.downgrade()),
24595                true,
24596                window,
24597                cx,
24598            )
24599        })
24600        .unwrap()
24601        .await
24602        .downcast::<Editor>()
24603        .unwrap();
24604    pane_1.update(cx, |pane, cx| {
24605        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24606        open_editor.update(cx, |editor, cx| {
24607            assert_eq!(
24608                editor.display_text(cx),
24609                main_text,
24610                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24611            );
24612            assert_eq!(
24613                editor
24614                    .selections
24615                    .all::<Point>(&editor.display_snapshot(cx))
24616                    .into_iter()
24617                    .map(|s| s.range())
24618                    .collect::<Vec<_>>(),
24619                expected_ranges,
24620                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24621            );
24622        })
24623    });
24624    pane_2.update(cx, |pane, cx| {
24625        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24626        open_editor.update(cx, |editor, cx| {
24627            assert_eq!(
24628                editor.display_text(cx),
24629                r#"fn main() {
24630⋯rintln!("1");
24631⋯intln!("2");
24632⋯ntln!("3");
24633println!("4");
24634println!("5");
24635}"#,
24636                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24637            );
24638            assert_eq!(
24639                editor
24640                    .selections
24641                    .all::<Point>(&editor.display_snapshot(cx))
24642                    .into_iter()
24643                    .map(|s| s.range())
24644                    .collect::<Vec<_>>(),
24645                vec![Point::zero()..Point::zero()],
24646                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24647            );
24648        })
24649    });
24650}
24651
24652#[gpui::test]
24653async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24654    init_test(cx, |_| {});
24655
24656    let fs = FakeFs::new(cx.executor());
24657    let main_text = r#"fn main() {
24658println!("1");
24659println!("2");
24660println!("3");
24661println!("4");
24662println!("5");
24663}"#;
24664    let lib_text = "mod foo {}";
24665    fs.insert_tree(
24666        path!("/a"),
24667        json!({
24668            "lib.rs": lib_text,
24669            "main.rs": main_text,
24670        }),
24671    )
24672    .await;
24673
24674    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24675    let (workspace, cx) =
24676        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24677    let worktree_id = workspace.update(cx, |workspace, cx| {
24678        workspace.project().update(cx, |project, cx| {
24679            project.worktrees(cx).next().unwrap().read(cx).id()
24680        })
24681    });
24682
24683    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24684    let editor = workspace
24685        .update_in(cx, |workspace, window, cx| {
24686            workspace.open_path(
24687                (worktree_id, rel_path("main.rs")),
24688                Some(pane.downgrade()),
24689                true,
24690                window,
24691                cx,
24692            )
24693        })
24694        .unwrap()
24695        .await
24696        .downcast::<Editor>()
24697        .unwrap();
24698    pane.update(cx, |pane, cx| {
24699        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24700        open_editor.update(cx, |editor, cx| {
24701            assert_eq!(
24702                editor.display_text(cx),
24703                main_text,
24704                "Original main.rs text on initial open",
24705            );
24706        })
24707    });
24708    editor.update_in(cx, |editor, window, cx| {
24709        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24710    });
24711
24712    cx.update_global(|store: &mut SettingsStore, cx| {
24713        store.update_user_settings(cx, |s| {
24714            s.workspace.restore_on_file_reopen = Some(false);
24715        });
24716    });
24717    editor.update_in(cx, |editor, window, cx| {
24718        editor.fold_ranges(
24719            vec![
24720                Point::new(1, 0)..Point::new(1, 1),
24721                Point::new(2, 0)..Point::new(2, 2),
24722                Point::new(3, 0)..Point::new(3, 3),
24723            ],
24724            false,
24725            window,
24726            cx,
24727        );
24728    });
24729    pane.update_in(cx, |pane, window, cx| {
24730        pane.close_all_items(&CloseAllItems::default(), window, cx)
24731    })
24732    .await
24733    .unwrap();
24734    pane.update(cx, |pane, _| {
24735        assert!(pane.active_item().is_none());
24736    });
24737    cx.update_global(|store: &mut SettingsStore, cx| {
24738        store.update_user_settings(cx, |s| {
24739            s.workspace.restore_on_file_reopen = Some(true);
24740        });
24741    });
24742
24743    let _editor_reopened = workspace
24744        .update_in(cx, |workspace, window, cx| {
24745            workspace.open_path(
24746                (worktree_id, rel_path("main.rs")),
24747                Some(pane.downgrade()),
24748                true,
24749                window,
24750                cx,
24751            )
24752        })
24753        .unwrap()
24754        .await
24755        .downcast::<Editor>()
24756        .unwrap();
24757    pane.update(cx, |pane, cx| {
24758        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24759        open_editor.update(cx, |editor, cx| {
24760            assert_eq!(
24761                editor.display_text(cx),
24762                main_text,
24763                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24764            );
24765        })
24766    });
24767}
24768
24769#[gpui::test]
24770async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24771    struct EmptyModalView {
24772        focus_handle: gpui::FocusHandle,
24773    }
24774    impl EventEmitter<DismissEvent> for EmptyModalView {}
24775    impl Render for EmptyModalView {
24776        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24777            div()
24778        }
24779    }
24780    impl Focusable for EmptyModalView {
24781        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24782            self.focus_handle.clone()
24783        }
24784    }
24785    impl workspace::ModalView for EmptyModalView {}
24786    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24787        EmptyModalView {
24788            focus_handle: cx.focus_handle(),
24789        }
24790    }
24791
24792    init_test(cx, |_| {});
24793
24794    let fs = FakeFs::new(cx.executor());
24795    let project = Project::test(fs, [], cx).await;
24796    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24797    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24798    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24799    let editor = cx.new_window_entity(|window, cx| {
24800        Editor::new(
24801            EditorMode::full(),
24802            buffer,
24803            Some(project.clone()),
24804            window,
24805            cx,
24806        )
24807    });
24808    workspace
24809        .update(cx, |workspace, window, cx| {
24810            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24811        })
24812        .unwrap();
24813    editor.update_in(cx, |editor, window, cx| {
24814        editor.open_context_menu(&OpenContextMenu, window, cx);
24815        assert!(editor.mouse_context_menu.is_some());
24816    });
24817    workspace
24818        .update(cx, |workspace, window, cx| {
24819            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24820        })
24821        .unwrap();
24822    cx.read(|cx| {
24823        assert!(editor.read(cx).mouse_context_menu.is_none());
24824    });
24825}
24826
24827fn set_linked_edit_ranges(
24828    opening: (Point, Point),
24829    closing: (Point, Point),
24830    editor: &mut Editor,
24831    cx: &mut Context<Editor>,
24832) {
24833    let Some((buffer, _)) = editor
24834        .buffer
24835        .read(cx)
24836        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24837    else {
24838        panic!("Failed to get buffer for selection position");
24839    };
24840    let buffer = buffer.read(cx);
24841    let buffer_id = buffer.remote_id();
24842    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24843    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24844    let mut linked_ranges = HashMap::default();
24845    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24846    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24847}
24848
24849#[gpui::test]
24850async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24851    init_test(cx, |_| {});
24852
24853    let fs = FakeFs::new(cx.executor());
24854    fs.insert_file(path!("/file.html"), Default::default())
24855        .await;
24856
24857    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24858
24859    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24860    let html_language = Arc::new(Language::new(
24861        LanguageConfig {
24862            name: "HTML".into(),
24863            matcher: LanguageMatcher {
24864                path_suffixes: vec!["html".to_string()],
24865                ..LanguageMatcher::default()
24866            },
24867            brackets: BracketPairConfig {
24868                pairs: vec![BracketPair {
24869                    start: "<".into(),
24870                    end: ">".into(),
24871                    close: true,
24872                    ..Default::default()
24873                }],
24874                ..Default::default()
24875            },
24876            ..Default::default()
24877        },
24878        Some(tree_sitter_html::LANGUAGE.into()),
24879    ));
24880    language_registry.add(html_language);
24881    let mut fake_servers = language_registry.register_fake_lsp(
24882        "HTML",
24883        FakeLspAdapter {
24884            capabilities: lsp::ServerCapabilities {
24885                completion_provider: Some(lsp::CompletionOptions {
24886                    resolve_provider: Some(true),
24887                    ..Default::default()
24888                }),
24889                ..Default::default()
24890            },
24891            ..Default::default()
24892        },
24893    );
24894
24895    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24896    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24897
24898    let worktree_id = workspace
24899        .update(cx, |workspace, _window, cx| {
24900            workspace.project().update(cx, |project, cx| {
24901                project.worktrees(cx).next().unwrap().read(cx).id()
24902            })
24903        })
24904        .unwrap();
24905    project
24906        .update(cx, |project, cx| {
24907            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24908        })
24909        .await
24910        .unwrap();
24911    let editor = workspace
24912        .update(cx, |workspace, window, cx| {
24913            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24914        })
24915        .unwrap()
24916        .await
24917        .unwrap()
24918        .downcast::<Editor>()
24919        .unwrap();
24920
24921    let fake_server = fake_servers.next().await.unwrap();
24922    editor.update_in(cx, |editor, window, cx| {
24923        editor.set_text("<ad></ad>", window, cx);
24924        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24925            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24926        });
24927        set_linked_edit_ranges(
24928            (Point::new(0, 1), Point::new(0, 3)),
24929            (Point::new(0, 6), Point::new(0, 8)),
24930            editor,
24931            cx,
24932        );
24933    });
24934    let mut completion_handle =
24935        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24936            Ok(Some(lsp::CompletionResponse::Array(vec![
24937                lsp::CompletionItem {
24938                    label: "head".to_string(),
24939                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24940                        lsp::InsertReplaceEdit {
24941                            new_text: "head".to_string(),
24942                            insert: lsp::Range::new(
24943                                lsp::Position::new(0, 1),
24944                                lsp::Position::new(0, 3),
24945                            ),
24946                            replace: lsp::Range::new(
24947                                lsp::Position::new(0, 1),
24948                                lsp::Position::new(0, 3),
24949                            ),
24950                        },
24951                    )),
24952                    ..Default::default()
24953                },
24954            ])))
24955        });
24956    editor.update_in(cx, |editor, window, cx| {
24957        editor.show_completions(&ShowCompletions, window, cx);
24958    });
24959    cx.run_until_parked();
24960    completion_handle.next().await.unwrap();
24961    editor.update(cx, |editor, _| {
24962        assert!(
24963            editor.context_menu_visible(),
24964            "Completion menu should be visible"
24965        );
24966    });
24967    editor.update_in(cx, |editor, window, cx| {
24968        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24969    });
24970    cx.executor().run_until_parked();
24971    editor.update(cx, |editor, cx| {
24972        assert_eq!(editor.text(cx), "<head></head>");
24973    });
24974}
24975
24976#[gpui::test]
24977async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24978    init_test(cx, |_| {});
24979
24980    let mut cx = EditorTestContext::new(cx).await;
24981    let language = Arc::new(Language::new(
24982        LanguageConfig {
24983            name: "TSX".into(),
24984            matcher: LanguageMatcher {
24985                path_suffixes: vec!["tsx".to_string()],
24986                ..LanguageMatcher::default()
24987            },
24988            brackets: BracketPairConfig {
24989                pairs: vec![BracketPair {
24990                    start: "<".into(),
24991                    end: ">".into(),
24992                    close: true,
24993                    ..Default::default()
24994                }],
24995                ..Default::default()
24996            },
24997            linked_edit_characters: HashSet::from_iter(['.']),
24998            ..Default::default()
24999        },
25000        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25001    ));
25002    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25003
25004    // Test typing > does not extend linked pair
25005    cx.set_state("<divˇ<div></div>");
25006    cx.update_editor(|editor, _, cx| {
25007        set_linked_edit_ranges(
25008            (Point::new(0, 1), Point::new(0, 4)),
25009            (Point::new(0, 11), Point::new(0, 14)),
25010            editor,
25011            cx,
25012        );
25013    });
25014    cx.update_editor(|editor, window, cx| {
25015        editor.handle_input(">", window, cx);
25016    });
25017    cx.assert_editor_state("<div>ˇ<div></div>");
25018
25019    // Test typing . do extend linked pair
25020    cx.set_state("<Animatedˇ></Animated>");
25021    cx.update_editor(|editor, _, cx| {
25022        set_linked_edit_ranges(
25023            (Point::new(0, 1), Point::new(0, 9)),
25024            (Point::new(0, 12), Point::new(0, 20)),
25025            editor,
25026            cx,
25027        );
25028    });
25029    cx.update_editor(|editor, window, cx| {
25030        editor.handle_input(".", window, cx);
25031    });
25032    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25033    cx.update_editor(|editor, _, cx| {
25034        set_linked_edit_ranges(
25035            (Point::new(0, 1), Point::new(0, 10)),
25036            (Point::new(0, 13), Point::new(0, 21)),
25037            editor,
25038            cx,
25039        );
25040    });
25041    cx.update_editor(|editor, window, cx| {
25042        editor.handle_input("V", window, cx);
25043    });
25044    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25045}
25046
25047#[gpui::test]
25048async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25049    init_test(cx, |_| {});
25050
25051    let fs = FakeFs::new(cx.executor());
25052    fs.insert_tree(
25053        path!("/root"),
25054        json!({
25055            "a": {
25056                "main.rs": "fn main() {}",
25057            },
25058            "foo": {
25059                "bar": {
25060                    "external_file.rs": "pub mod external {}",
25061                }
25062            }
25063        }),
25064    )
25065    .await;
25066
25067    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25068    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25069    language_registry.add(rust_lang());
25070    let _fake_servers = language_registry.register_fake_lsp(
25071        "Rust",
25072        FakeLspAdapter {
25073            ..FakeLspAdapter::default()
25074        },
25075    );
25076    let (workspace, cx) =
25077        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25078    let worktree_id = workspace.update(cx, |workspace, cx| {
25079        workspace.project().update(cx, |project, cx| {
25080            project.worktrees(cx).next().unwrap().read(cx).id()
25081        })
25082    });
25083
25084    let assert_language_servers_count =
25085        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25086            project.update(cx, |project, cx| {
25087                let current = project
25088                    .lsp_store()
25089                    .read(cx)
25090                    .as_local()
25091                    .unwrap()
25092                    .language_servers
25093                    .len();
25094                assert_eq!(expected, current, "{context}");
25095            });
25096        };
25097
25098    assert_language_servers_count(
25099        0,
25100        "No servers should be running before any file is open",
25101        cx,
25102    );
25103    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25104    let main_editor = workspace
25105        .update_in(cx, |workspace, window, cx| {
25106            workspace.open_path(
25107                (worktree_id, rel_path("main.rs")),
25108                Some(pane.downgrade()),
25109                true,
25110                window,
25111                cx,
25112            )
25113        })
25114        .unwrap()
25115        .await
25116        .downcast::<Editor>()
25117        .unwrap();
25118    pane.update(cx, |pane, cx| {
25119        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25120        open_editor.update(cx, |editor, cx| {
25121            assert_eq!(
25122                editor.display_text(cx),
25123                "fn main() {}",
25124                "Original main.rs text on initial open",
25125            );
25126        });
25127        assert_eq!(open_editor, main_editor);
25128    });
25129    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25130
25131    let external_editor = workspace
25132        .update_in(cx, |workspace, window, cx| {
25133            workspace.open_abs_path(
25134                PathBuf::from("/root/foo/bar/external_file.rs"),
25135                OpenOptions::default(),
25136                window,
25137                cx,
25138            )
25139        })
25140        .await
25141        .expect("opening external file")
25142        .downcast::<Editor>()
25143        .expect("downcasted external file's open element to editor");
25144    pane.update(cx, |pane, cx| {
25145        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25146        open_editor.update(cx, |editor, cx| {
25147            assert_eq!(
25148                editor.display_text(cx),
25149                "pub mod external {}",
25150                "External file is open now",
25151            );
25152        });
25153        assert_eq!(open_editor, external_editor);
25154    });
25155    assert_language_servers_count(
25156        1,
25157        "Second, external, *.rs file should join the existing server",
25158        cx,
25159    );
25160
25161    pane.update_in(cx, |pane, window, cx| {
25162        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25163    })
25164    .await
25165    .unwrap();
25166    pane.update_in(cx, |pane, window, cx| {
25167        pane.navigate_backward(&Default::default(), window, cx);
25168    });
25169    cx.run_until_parked();
25170    pane.update(cx, |pane, cx| {
25171        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25172        open_editor.update(cx, |editor, cx| {
25173            assert_eq!(
25174                editor.display_text(cx),
25175                "pub mod external {}",
25176                "External file is open now",
25177            );
25178        });
25179    });
25180    assert_language_servers_count(
25181        1,
25182        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25183        cx,
25184    );
25185
25186    cx.update(|_, cx| {
25187        workspace::reload(cx);
25188    });
25189    assert_language_servers_count(
25190        1,
25191        "After reloading the worktree with local and external files opened, only one project should be started",
25192        cx,
25193    );
25194}
25195
25196#[gpui::test]
25197async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25198    init_test(cx, |_| {});
25199
25200    let mut cx = EditorTestContext::new(cx).await;
25201    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25202    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25203
25204    // test cursor move to start of each line on tab
25205    // for `if`, `elif`, `else`, `while`, `with` and `for`
25206    cx.set_state(indoc! {"
25207        def main():
25208        ˇ    for item in items:
25209        ˇ        while item.active:
25210        ˇ            if item.value > 10:
25211        ˇ                continue
25212        ˇ            elif item.value < 0:
25213        ˇ                break
25214        ˇ            else:
25215        ˇ                with item.context() as ctx:
25216        ˇ                    yield count
25217        ˇ        else:
25218        ˇ            log('while else')
25219        ˇ    else:
25220        ˇ        log('for else')
25221    "});
25222    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25223    cx.assert_editor_state(indoc! {"
25224        def main():
25225            ˇfor item in items:
25226                ˇwhile item.active:
25227                    ˇif item.value > 10:
25228                        ˇcontinue
25229                    ˇelif item.value < 0:
25230                        ˇbreak
25231                    ˇelse:
25232                        ˇwith item.context() as ctx:
25233                            ˇyield count
25234                ˇelse:
25235                    ˇlog('while else')
25236            ˇelse:
25237                ˇlog('for else')
25238    "});
25239    // test relative indent is preserved when tab
25240    // for `if`, `elif`, `else`, `while`, `with` and `for`
25241    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25242    cx.assert_editor_state(indoc! {"
25243        def main():
25244                ˇfor item in items:
25245                    ˇwhile item.active:
25246                        ˇif item.value > 10:
25247                            ˇcontinue
25248                        ˇelif item.value < 0:
25249                            ˇbreak
25250                        ˇelse:
25251                            ˇwith item.context() as ctx:
25252                                ˇyield count
25253                    ˇelse:
25254                        ˇlog('while else')
25255                ˇelse:
25256                    ˇlog('for else')
25257    "});
25258
25259    // test cursor move to start of each line on tab
25260    // for `try`, `except`, `else`, `finally`, `match` and `def`
25261    cx.set_state(indoc! {"
25262        def main():
25263        ˇ    try:
25264        ˇ        fetch()
25265        ˇ    except ValueError:
25266        ˇ        handle_error()
25267        ˇ    else:
25268        ˇ        match value:
25269        ˇ            case _:
25270        ˇ    finally:
25271        ˇ        def status():
25272        ˇ            return 0
25273    "});
25274    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25275    cx.assert_editor_state(indoc! {"
25276        def main():
25277            ˇtry:
25278                ˇfetch()
25279            ˇexcept ValueError:
25280                ˇhandle_error()
25281            ˇelse:
25282                ˇmatch value:
25283                    ˇcase _:
25284            ˇfinally:
25285                ˇdef status():
25286                    ˇreturn 0
25287    "});
25288    // test relative indent is preserved when tab
25289    // for `try`, `except`, `else`, `finally`, `match` and `def`
25290    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25291    cx.assert_editor_state(indoc! {"
25292        def main():
25293                ˇtry:
25294                    ˇfetch()
25295                ˇexcept ValueError:
25296                    ˇhandle_error()
25297                ˇelse:
25298                    ˇmatch value:
25299                        ˇcase _:
25300                ˇfinally:
25301                    ˇdef status():
25302                        ˇreturn 0
25303    "});
25304}
25305
25306#[gpui::test]
25307async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25308    init_test(cx, |_| {});
25309
25310    let mut cx = EditorTestContext::new(cx).await;
25311    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25312    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25313
25314    // test `else` auto outdents when typed inside `if` block
25315    cx.set_state(indoc! {"
25316        def main():
25317            if i == 2:
25318                return
25319                ˇ
25320    "});
25321    cx.update_editor(|editor, window, cx| {
25322        editor.handle_input("else:", window, cx);
25323    });
25324    cx.assert_editor_state(indoc! {"
25325        def main():
25326            if i == 2:
25327                return
25328            else:ˇ
25329    "});
25330
25331    // test `except` auto outdents when typed inside `try` block
25332    cx.set_state(indoc! {"
25333        def main():
25334            try:
25335                i = 2
25336                ˇ
25337    "});
25338    cx.update_editor(|editor, window, cx| {
25339        editor.handle_input("except:", window, cx);
25340    });
25341    cx.assert_editor_state(indoc! {"
25342        def main():
25343            try:
25344                i = 2
25345            except:ˇ
25346    "});
25347
25348    // test `else` auto outdents when typed inside `except` block
25349    cx.set_state(indoc! {"
25350        def main():
25351            try:
25352                i = 2
25353            except:
25354                j = 2
25355                ˇ
25356    "});
25357    cx.update_editor(|editor, window, cx| {
25358        editor.handle_input("else:", window, cx);
25359    });
25360    cx.assert_editor_state(indoc! {"
25361        def main():
25362            try:
25363                i = 2
25364            except:
25365                j = 2
25366            else:ˇ
25367    "});
25368
25369    // test `finally` auto outdents when typed inside `else` block
25370    cx.set_state(indoc! {"
25371        def main():
25372            try:
25373                i = 2
25374            except:
25375                j = 2
25376            else:
25377                k = 2
25378                ˇ
25379    "});
25380    cx.update_editor(|editor, window, cx| {
25381        editor.handle_input("finally:", window, cx);
25382    });
25383    cx.assert_editor_state(indoc! {"
25384        def main():
25385            try:
25386                i = 2
25387            except:
25388                j = 2
25389            else:
25390                k = 2
25391            finally:ˇ
25392    "});
25393
25394    // test `else` does not outdents when typed inside `except` block right after for block
25395    cx.set_state(indoc! {"
25396        def main():
25397            try:
25398                i = 2
25399            except:
25400                for i in range(n):
25401                    pass
25402                ˇ
25403    "});
25404    cx.update_editor(|editor, window, cx| {
25405        editor.handle_input("else:", window, cx);
25406    });
25407    cx.assert_editor_state(indoc! {"
25408        def main():
25409            try:
25410                i = 2
25411            except:
25412                for i in range(n):
25413                    pass
25414                else:ˇ
25415    "});
25416
25417    // test `finally` auto outdents when typed inside `else` block right after for block
25418    cx.set_state(indoc! {"
25419        def main():
25420            try:
25421                i = 2
25422            except:
25423                j = 2
25424            else:
25425                for i in range(n):
25426                    pass
25427                ˇ
25428    "});
25429    cx.update_editor(|editor, window, cx| {
25430        editor.handle_input("finally:", window, cx);
25431    });
25432    cx.assert_editor_state(indoc! {"
25433        def main():
25434            try:
25435                i = 2
25436            except:
25437                j = 2
25438            else:
25439                for i in range(n):
25440                    pass
25441            finally:ˇ
25442    "});
25443
25444    // test `except` outdents to inner "try" block
25445    cx.set_state(indoc! {"
25446        def main():
25447            try:
25448                i = 2
25449                if i == 2:
25450                    try:
25451                        i = 3
25452                        ˇ
25453    "});
25454    cx.update_editor(|editor, window, cx| {
25455        editor.handle_input("except:", window, cx);
25456    });
25457    cx.assert_editor_state(indoc! {"
25458        def main():
25459            try:
25460                i = 2
25461                if i == 2:
25462                    try:
25463                        i = 3
25464                    except:ˇ
25465    "});
25466
25467    // test `except` outdents to outer "try" block
25468    cx.set_state(indoc! {"
25469        def main():
25470            try:
25471                i = 2
25472                if i == 2:
25473                    try:
25474                        i = 3
25475                ˇ
25476    "});
25477    cx.update_editor(|editor, window, cx| {
25478        editor.handle_input("except:", window, cx);
25479    });
25480    cx.assert_editor_state(indoc! {"
25481        def main():
25482            try:
25483                i = 2
25484                if i == 2:
25485                    try:
25486                        i = 3
25487            except:ˇ
25488    "});
25489
25490    // test `else` stays at correct indent when typed after `for` block
25491    cx.set_state(indoc! {"
25492        def main():
25493            for i in range(10):
25494                if i == 3:
25495                    break
25496            ˇ
25497    "});
25498    cx.update_editor(|editor, window, cx| {
25499        editor.handle_input("else:", window, cx);
25500    });
25501    cx.assert_editor_state(indoc! {"
25502        def main():
25503            for i in range(10):
25504                if i == 3:
25505                    break
25506            else:ˇ
25507    "});
25508
25509    // test does not outdent on typing after line with square brackets
25510    cx.set_state(indoc! {"
25511        def f() -> list[str]:
25512            ˇ
25513    "});
25514    cx.update_editor(|editor, window, cx| {
25515        editor.handle_input("a", window, cx);
25516    });
25517    cx.assert_editor_state(indoc! {"
25518        def f() -> list[str]:
2551925520    "});
25521
25522    // test does not outdent on typing : after case keyword
25523    cx.set_state(indoc! {"
25524        match 1:
25525            caseˇ
25526    "});
25527    cx.update_editor(|editor, window, cx| {
25528        editor.handle_input(":", window, cx);
25529    });
25530    cx.assert_editor_state(indoc! {"
25531        match 1:
25532            case:ˇ
25533    "});
25534}
25535
25536#[gpui::test]
25537async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25538    init_test(cx, |_| {});
25539    update_test_language_settings(cx, |settings| {
25540        settings.defaults.extend_comment_on_newline = Some(false);
25541    });
25542    let mut cx = EditorTestContext::new(cx).await;
25543    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25544    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25545
25546    // test correct indent after newline on comment
25547    cx.set_state(indoc! {"
25548        # COMMENT:ˇ
25549    "});
25550    cx.update_editor(|editor, window, cx| {
25551        editor.newline(&Newline, window, cx);
25552    });
25553    cx.assert_editor_state(indoc! {"
25554        # COMMENT:
25555        ˇ
25556    "});
25557
25558    // test correct indent after newline in brackets
25559    cx.set_state(indoc! {"
25560        {ˇ}
25561    "});
25562    cx.update_editor(|editor, window, cx| {
25563        editor.newline(&Newline, window, cx);
25564    });
25565    cx.run_until_parked();
25566    cx.assert_editor_state(indoc! {"
25567        {
25568            ˇ
25569        }
25570    "});
25571
25572    cx.set_state(indoc! {"
25573        (ˇ)
25574    "});
25575    cx.update_editor(|editor, window, cx| {
25576        editor.newline(&Newline, window, cx);
25577    });
25578    cx.run_until_parked();
25579    cx.assert_editor_state(indoc! {"
25580        (
25581            ˇ
25582        )
25583    "});
25584
25585    // do not indent after empty lists or dictionaries
25586    cx.set_state(indoc! {"
25587        a = []ˇ
25588    "});
25589    cx.update_editor(|editor, window, cx| {
25590        editor.newline(&Newline, window, cx);
25591    });
25592    cx.run_until_parked();
25593    cx.assert_editor_state(indoc! {"
25594        a = []
25595        ˇ
25596    "});
25597}
25598
25599#[gpui::test]
25600async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25601    init_test(cx, |_| {});
25602
25603    let mut cx = EditorTestContext::new(cx).await;
25604    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25605    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25606
25607    // test cursor move to start of each line on tab
25608    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25609    cx.set_state(indoc! {"
25610        function main() {
25611        ˇ    for item in $items; do
25612        ˇ        while [ -n \"$item\" ]; do
25613        ˇ            if [ \"$value\" -gt 10 ]; then
25614        ˇ                continue
25615        ˇ            elif [ \"$value\" -lt 0 ]; then
25616        ˇ                break
25617        ˇ            else
25618        ˇ                echo \"$item\"
25619        ˇ            fi
25620        ˇ        done
25621        ˇ    done
25622        ˇ}
25623    "});
25624    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25625    cx.assert_editor_state(indoc! {"
25626        function main() {
25627            ˇfor item in $items; do
25628                ˇwhile [ -n \"$item\" ]; do
25629                    ˇif [ \"$value\" -gt 10 ]; then
25630                        ˇcontinue
25631                    ˇelif [ \"$value\" -lt 0 ]; then
25632                        ˇbreak
25633                    ˇelse
25634                        ˇecho \"$item\"
25635                    ˇfi
25636                ˇdone
25637            ˇdone
25638        ˇ}
25639    "});
25640    // test relative indent is preserved when tab
25641    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25642    cx.assert_editor_state(indoc! {"
25643        function main() {
25644                ˇfor item in $items; do
25645                    ˇwhile [ -n \"$item\" ]; do
25646                        ˇif [ \"$value\" -gt 10 ]; then
25647                            ˇcontinue
25648                        ˇelif [ \"$value\" -lt 0 ]; then
25649                            ˇbreak
25650                        ˇelse
25651                            ˇecho \"$item\"
25652                        ˇfi
25653                    ˇdone
25654                ˇdone
25655            ˇ}
25656    "});
25657
25658    // test cursor move to start of each line on tab
25659    // for `case` statement with patterns
25660    cx.set_state(indoc! {"
25661        function handle() {
25662        ˇ    case \"$1\" in
25663        ˇ        start)
25664        ˇ            echo \"a\"
25665        ˇ            ;;
25666        ˇ        stop)
25667        ˇ            echo \"b\"
25668        ˇ            ;;
25669        ˇ        *)
25670        ˇ            echo \"c\"
25671        ˇ            ;;
25672        ˇ    esac
25673        ˇ}
25674    "});
25675    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25676    cx.assert_editor_state(indoc! {"
25677        function handle() {
25678            ˇcase \"$1\" in
25679                ˇstart)
25680                    ˇecho \"a\"
25681                    ˇ;;
25682                ˇstop)
25683                    ˇecho \"b\"
25684                    ˇ;;
25685                ˇ*)
25686                    ˇecho \"c\"
25687                    ˇ;;
25688            ˇesac
25689        ˇ}
25690    "});
25691}
25692
25693#[gpui::test]
25694async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25695    init_test(cx, |_| {});
25696
25697    let mut cx = EditorTestContext::new(cx).await;
25698    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25699    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25700
25701    // test indents on comment insert
25702    cx.set_state(indoc! {"
25703        function main() {
25704        ˇ    for item in $items; do
25705        ˇ        while [ -n \"$item\" ]; do
25706        ˇ            if [ \"$value\" -gt 10 ]; then
25707        ˇ                continue
25708        ˇ            elif [ \"$value\" -lt 0 ]; then
25709        ˇ                break
25710        ˇ            else
25711        ˇ                echo \"$item\"
25712        ˇ            fi
25713        ˇ        done
25714        ˇ    done
25715        ˇ}
25716    "});
25717    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25718    cx.assert_editor_state(indoc! {"
25719        function main() {
25720        #ˇ    for item in $items; do
25721        #ˇ        while [ -n \"$item\" ]; do
25722        #ˇ            if [ \"$value\" -gt 10 ]; then
25723        #ˇ                continue
25724        #ˇ            elif [ \"$value\" -lt 0 ]; then
25725        #ˇ                break
25726        #ˇ            else
25727        #ˇ                echo \"$item\"
25728        #ˇ            fi
25729        #ˇ        done
25730        #ˇ    done
25731        #ˇ}
25732    "});
25733}
25734
25735#[gpui::test]
25736async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25737    init_test(cx, |_| {});
25738
25739    let mut cx = EditorTestContext::new(cx).await;
25740    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25741    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25742
25743    // test `else` auto outdents when typed inside `if` block
25744    cx.set_state(indoc! {"
25745        if [ \"$1\" = \"test\" ]; then
25746            echo \"foo bar\"
25747            ˇ
25748    "});
25749    cx.update_editor(|editor, window, cx| {
25750        editor.handle_input("else", window, cx);
25751    });
25752    cx.assert_editor_state(indoc! {"
25753        if [ \"$1\" = \"test\" ]; then
25754            echo \"foo bar\"
25755        elseˇ
25756    "});
25757
25758    // test `elif` auto outdents when typed inside `if` block
25759    cx.set_state(indoc! {"
25760        if [ \"$1\" = \"test\" ]; then
25761            echo \"foo bar\"
25762            ˇ
25763    "});
25764    cx.update_editor(|editor, window, cx| {
25765        editor.handle_input("elif", window, cx);
25766    });
25767    cx.assert_editor_state(indoc! {"
25768        if [ \"$1\" = \"test\" ]; then
25769            echo \"foo bar\"
25770        elifˇ
25771    "});
25772
25773    // test `fi` auto outdents when typed inside `else` block
25774    cx.set_state(indoc! {"
25775        if [ \"$1\" = \"test\" ]; then
25776            echo \"foo bar\"
25777        else
25778            echo \"bar baz\"
25779            ˇ
25780    "});
25781    cx.update_editor(|editor, window, cx| {
25782        editor.handle_input("fi", window, cx);
25783    });
25784    cx.assert_editor_state(indoc! {"
25785        if [ \"$1\" = \"test\" ]; then
25786            echo \"foo bar\"
25787        else
25788            echo \"bar baz\"
25789        fiˇ
25790    "});
25791
25792    // test `done` auto outdents when typed inside `while` block
25793    cx.set_state(indoc! {"
25794        while read line; do
25795            echo \"$line\"
25796            ˇ
25797    "});
25798    cx.update_editor(|editor, window, cx| {
25799        editor.handle_input("done", window, cx);
25800    });
25801    cx.assert_editor_state(indoc! {"
25802        while read line; do
25803            echo \"$line\"
25804        doneˇ
25805    "});
25806
25807    // test `done` auto outdents when typed inside `for` block
25808    cx.set_state(indoc! {"
25809        for file in *.txt; do
25810            cat \"$file\"
25811            ˇ
25812    "});
25813    cx.update_editor(|editor, window, cx| {
25814        editor.handle_input("done", window, cx);
25815    });
25816    cx.assert_editor_state(indoc! {"
25817        for file in *.txt; do
25818            cat \"$file\"
25819        doneˇ
25820    "});
25821
25822    // test `esac` auto outdents when typed inside `case` block
25823    cx.set_state(indoc! {"
25824        case \"$1\" in
25825            start)
25826                echo \"foo bar\"
25827                ;;
25828            stop)
25829                echo \"bar baz\"
25830                ;;
25831            ˇ
25832    "});
25833    cx.update_editor(|editor, window, cx| {
25834        editor.handle_input("esac", window, cx);
25835    });
25836    cx.assert_editor_state(indoc! {"
25837        case \"$1\" in
25838            start)
25839                echo \"foo bar\"
25840                ;;
25841            stop)
25842                echo \"bar baz\"
25843                ;;
25844        esacˇ
25845    "});
25846
25847    // test `*)` auto outdents when typed inside `case` block
25848    cx.set_state(indoc! {"
25849        case \"$1\" in
25850            start)
25851                echo \"foo bar\"
25852                ;;
25853                ˇ
25854    "});
25855    cx.update_editor(|editor, window, cx| {
25856        editor.handle_input("*)", window, cx);
25857    });
25858    cx.assert_editor_state(indoc! {"
25859        case \"$1\" in
25860            start)
25861                echo \"foo bar\"
25862                ;;
25863            *)ˇ
25864    "});
25865
25866    // test `fi` outdents to correct level with nested if blocks
25867    cx.set_state(indoc! {"
25868        if [ \"$1\" = \"test\" ]; then
25869            echo \"outer if\"
25870            if [ \"$2\" = \"debug\" ]; then
25871                echo \"inner if\"
25872                ˇ
25873    "});
25874    cx.update_editor(|editor, window, cx| {
25875        editor.handle_input("fi", window, cx);
25876    });
25877    cx.assert_editor_state(indoc! {"
25878        if [ \"$1\" = \"test\" ]; then
25879            echo \"outer if\"
25880            if [ \"$2\" = \"debug\" ]; then
25881                echo \"inner if\"
25882            fiˇ
25883    "});
25884}
25885
25886#[gpui::test]
25887async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25888    init_test(cx, |_| {});
25889    update_test_language_settings(cx, |settings| {
25890        settings.defaults.extend_comment_on_newline = Some(false);
25891    });
25892    let mut cx = EditorTestContext::new(cx).await;
25893    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25894    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25895
25896    // test correct indent after newline on comment
25897    cx.set_state(indoc! {"
25898        # COMMENT:ˇ
25899    "});
25900    cx.update_editor(|editor, window, cx| {
25901        editor.newline(&Newline, window, cx);
25902    });
25903    cx.assert_editor_state(indoc! {"
25904        # COMMENT:
25905        ˇ
25906    "});
25907
25908    // test correct indent after newline after `then`
25909    cx.set_state(indoc! {"
25910
25911        if [ \"$1\" = \"test\" ]; thenˇ
25912    "});
25913    cx.update_editor(|editor, window, cx| {
25914        editor.newline(&Newline, window, cx);
25915    });
25916    cx.run_until_parked();
25917    cx.assert_editor_state(indoc! {"
25918
25919        if [ \"$1\" = \"test\" ]; then
25920            ˇ
25921    "});
25922
25923    // test correct indent after newline after `else`
25924    cx.set_state(indoc! {"
25925        if [ \"$1\" = \"test\" ]; then
25926        elseˇ
25927    "});
25928    cx.update_editor(|editor, window, cx| {
25929        editor.newline(&Newline, window, cx);
25930    });
25931    cx.run_until_parked();
25932    cx.assert_editor_state(indoc! {"
25933        if [ \"$1\" = \"test\" ]; then
25934        else
25935            ˇ
25936    "});
25937
25938    // test correct indent after newline after `elif`
25939    cx.set_state(indoc! {"
25940        if [ \"$1\" = \"test\" ]; then
25941        elifˇ
25942    "});
25943    cx.update_editor(|editor, window, cx| {
25944        editor.newline(&Newline, window, cx);
25945    });
25946    cx.run_until_parked();
25947    cx.assert_editor_state(indoc! {"
25948        if [ \"$1\" = \"test\" ]; then
25949        elif
25950            ˇ
25951    "});
25952
25953    // test correct indent after newline after `do`
25954    cx.set_state(indoc! {"
25955        for file in *.txt; doˇ
25956    "});
25957    cx.update_editor(|editor, window, cx| {
25958        editor.newline(&Newline, window, cx);
25959    });
25960    cx.run_until_parked();
25961    cx.assert_editor_state(indoc! {"
25962        for file in *.txt; do
25963            ˇ
25964    "});
25965
25966    // test correct indent after newline after case pattern
25967    cx.set_state(indoc! {"
25968        case \"$1\" in
25969            start)ˇ
25970    "});
25971    cx.update_editor(|editor, window, cx| {
25972        editor.newline(&Newline, window, cx);
25973    });
25974    cx.run_until_parked();
25975    cx.assert_editor_state(indoc! {"
25976        case \"$1\" in
25977            start)
25978                ˇ
25979    "});
25980
25981    // test correct indent after newline after case pattern
25982    cx.set_state(indoc! {"
25983        case \"$1\" in
25984            start)
25985                ;;
25986            *)ˇ
25987    "});
25988    cx.update_editor(|editor, window, cx| {
25989        editor.newline(&Newline, window, cx);
25990    });
25991    cx.run_until_parked();
25992    cx.assert_editor_state(indoc! {"
25993        case \"$1\" in
25994            start)
25995                ;;
25996            *)
25997                ˇ
25998    "});
25999
26000    // test correct indent after newline after function opening brace
26001    cx.set_state(indoc! {"
26002        function test() {ˇ}
26003    "});
26004    cx.update_editor(|editor, window, cx| {
26005        editor.newline(&Newline, window, cx);
26006    });
26007    cx.run_until_parked();
26008    cx.assert_editor_state(indoc! {"
26009        function test() {
26010            ˇ
26011        }
26012    "});
26013
26014    // test no extra indent after semicolon on same line
26015    cx.set_state(indoc! {"
26016        echo \"test\"26017    "});
26018    cx.update_editor(|editor, window, cx| {
26019        editor.newline(&Newline, window, cx);
26020    });
26021    cx.run_until_parked();
26022    cx.assert_editor_state(indoc! {"
26023        echo \"test\";
26024        ˇ
26025    "});
26026}
26027
26028fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26029    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26030    point..point
26031}
26032
26033#[track_caller]
26034fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26035    let (text, ranges) = marked_text_ranges(marked_text, true);
26036    assert_eq!(editor.text(cx), text);
26037    assert_eq!(
26038        editor.selections.ranges(&editor.display_snapshot(cx)),
26039        ranges
26040            .iter()
26041            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26042            .collect::<Vec<_>>(),
26043        "Assert selections are {}",
26044        marked_text
26045    );
26046}
26047
26048pub fn handle_signature_help_request(
26049    cx: &mut EditorLspTestContext,
26050    mocked_response: lsp::SignatureHelp,
26051) -> impl Future<Output = ()> + use<> {
26052    let mut request =
26053        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26054            let mocked_response = mocked_response.clone();
26055            async move { Ok(Some(mocked_response)) }
26056        });
26057
26058    async move {
26059        request.next().await;
26060    }
26061}
26062
26063#[track_caller]
26064pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26065    cx.update_editor(|editor, _, _| {
26066        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26067            let entries = menu.entries.borrow();
26068            let entries = entries
26069                .iter()
26070                .map(|entry| entry.string.as_str())
26071                .collect::<Vec<_>>();
26072            assert_eq!(entries, expected);
26073        } else {
26074            panic!("Expected completions menu");
26075        }
26076    });
26077}
26078
26079#[gpui::test]
26080async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26081    init_test(cx, |_| {});
26082    let mut cx = EditorLspTestContext::new_rust(
26083        lsp::ServerCapabilities {
26084            completion_provider: Some(lsp::CompletionOptions {
26085                ..Default::default()
26086            }),
26087            ..Default::default()
26088        },
26089        cx,
26090    )
26091    .await;
26092    cx.lsp
26093        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26094            Ok(Some(lsp::CompletionResponse::Array(vec![
26095                lsp::CompletionItem {
26096                    label: "unsafe".into(),
26097                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26098                        range: lsp::Range {
26099                            start: lsp::Position {
26100                                line: 0,
26101                                character: 9,
26102                            },
26103                            end: lsp::Position {
26104                                line: 0,
26105                                character: 11,
26106                            },
26107                        },
26108                        new_text: "unsafe".to_string(),
26109                    })),
26110                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26111                    ..Default::default()
26112                },
26113            ])))
26114        });
26115
26116    cx.update_editor(|editor, _, cx| {
26117        editor.project().unwrap().update(cx, |project, cx| {
26118            project.snippets().update(cx, |snippets, _cx| {
26119                snippets.add_snippet_for_test(
26120                    None,
26121                    PathBuf::from("test_snippets.json"),
26122                    vec![
26123                        Arc::new(project::snippet_provider::Snippet {
26124                            prefix: vec![
26125                                "unlimited word count".to_string(),
26126                                "unlimit word count".to_string(),
26127                                "unlimited unknown".to_string(),
26128                            ],
26129                            body: "this is many words".to_string(),
26130                            description: Some("description".to_string()),
26131                            name: "multi-word snippet test".to_string(),
26132                        }),
26133                        Arc::new(project::snippet_provider::Snippet {
26134                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26135                            body: "fewer words".to_string(),
26136                            description: Some("alt description".to_string()),
26137                            name: "other name".to_string(),
26138                        }),
26139                        Arc::new(project::snippet_provider::Snippet {
26140                            prefix: vec!["ab aa".to_string()],
26141                            body: "abcd".to_string(),
26142                            description: None,
26143                            name: "alphabet".to_string(),
26144                        }),
26145                    ],
26146                );
26147            });
26148        })
26149    });
26150
26151    let get_completions = |cx: &mut EditorLspTestContext| {
26152        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26153            Some(CodeContextMenu::Completions(context_menu)) => {
26154                let entries = context_menu.entries.borrow();
26155                entries
26156                    .iter()
26157                    .map(|entry| entry.string.clone())
26158                    .collect_vec()
26159            }
26160            _ => vec![],
26161        })
26162    };
26163
26164    // snippets:
26165    //  @foo
26166    //  foo bar
26167    //
26168    // when typing:
26169    //
26170    // when typing:
26171    //  - if I type a symbol "open the completions with snippets only"
26172    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26173    //
26174    // stuff we need:
26175    //  - filtering logic change?
26176    //  - remember how far back the completion started.
26177
26178    let test_cases: &[(&str, &[&str])] = &[
26179        (
26180            "un",
26181            &[
26182                "unsafe",
26183                "unlimit word count",
26184                "unlimited unknown",
26185                "unlimited word count",
26186                "unsnip",
26187            ],
26188        ),
26189        (
26190            "u ",
26191            &[
26192                "unlimit word count",
26193                "unlimited unknown",
26194                "unlimited word count",
26195            ],
26196        ),
26197        ("u a", &["ab aa", "unsafe"]), // unsAfe
26198        (
26199            "u u",
26200            &[
26201                "unsafe",
26202                "unlimit word count",
26203                "unlimited unknown", // ranked highest among snippets
26204                "unlimited word count",
26205                "unsnip",
26206            ],
26207        ),
26208        ("uw c", &["unlimit word count", "unlimited word count"]),
26209        (
26210            "u w",
26211            &[
26212                "unlimit word count",
26213                "unlimited word count",
26214                "unlimited unknown",
26215            ],
26216        ),
26217        ("u w ", &["unlimit word count", "unlimited word count"]),
26218        (
26219            "u ",
26220            &[
26221                "unlimit word count",
26222                "unlimited unknown",
26223                "unlimited word count",
26224            ],
26225        ),
26226        ("wor", &[]),
26227        ("uf", &["unsafe"]),
26228        ("af", &["unsafe"]),
26229        ("afu", &[]),
26230        (
26231            "ue",
26232            &["unsafe", "unlimited unknown", "unlimited word count"],
26233        ),
26234        ("@", &["@few"]),
26235        ("@few", &["@few"]),
26236        ("@ ", &[]),
26237        ("a@", &["@few"]),
26238        ("a@f", &["@few", "unsafe"]),
26239        ("a@fw", &["@few"]),
26240        ("a", &["ab aa", "unsafe"]),
26241        ("aa", &["ab aa"]),
26242        ("aaa", &["ab aa"]),
26243        ("ab", &["ab aa"]),
26244        ("ab ", &["ab aa"]),
26245        ("ab a", &["ab aa", "unsafe"]),
26246        ("ab ab", &["ab aa"]),
26247        ("ab ab aa", &["ab aa"]),
26248    ];
26249
26250    for &(input_to_simulate, expected_completions) in test_cases {
26251        cx.set_state("fn a() { ˇ }\n");
26252        for c in input_to_simulate.split("") {
26253            cx.simulate_input(c);
26254            cx.run_until_parked();
26255        }
26256        let expected_completions = expected_completions
26257            .iter()
26258            .map(|s| s.to_string())
26259            .collect_vec();
26260        assert_eq!(
26261            get_completions(&mut cx),
26262            expected_completions,
26263            "< actual / expected >, input = {input_to_simulate:?}",
26264        );
26265    }
26266}
26267
26268/// Handle completion request passing a marked string specifying where the completion
26269/// should be triggered from using '|' character, what range should be replaced, and what completions
26270/// should be returned using '<' and '>' to delimit the range.
26271///
26272/// Also see `handle_completion_request_with_insert_and_replace`.
26273#[track_caller]
26274pub fn handle_completion_request(
26275    marked_string: &str,
26276    completions: Vec<&'static str>,
26277    is_incomplete: bool,
26278    counter: Arc<AtomicUsize>,
26279    cx: &mut EditorLspTestContext,
26280) -> impl Future<Output = ()> {
26281    let complete_from_marker: TextRangeMarker = '|'.into();
26282    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26283    let (_, mut marked_ranges) = marked_text_ranges_by(
26284        marked_string,
26285        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26286    );
26287
26288    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26289        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26290    ));
26291    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26292    let replace_range =
26293        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26294
26295    let mut request =
26296        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26297            let completions = completions.clone();
26298            counter.fetch_add(1, atomic::Ordering::Release);
26299            async move {
26300                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26301                assert_eq!(
26302                    params.text_document_position.position,
26303                    complete_from_position
26304                );
26305                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26306                    is_incomplete,
26307                    item_defaults: None,
26308                    items: completions
26309                        .iter()
26310                        .map(|completion_text| lsp::CompletionItem {
26311                            label: completion_text.to_string(),
26312                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26313                                range: replace_range,
26314                                new_text: completion_text.to_string(),
26315                            })),
26316                            ..Default::default()
26317                        })
26318                        .collect(),
26319                })))
26320            }
26321        });
26322
26323    async move {
26324        request.next().await;
26325    }
26326}
26327
26328/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26329/// given instead, which also contains an `insert` range.
26330///
26331/// This function uses markers to define ranges:
26332/// - `|` marks the cursor position
26333/// - `<>` marks the replace range
26334/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26335pub fn handle_completion_request_with_insert_and_replace(
26336    cx: &mut EditorLspTestContext,
26337    marked_string: &str,
26338    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26339    counter: Arc<AtomicUsize>,
26340) -> impl Future<Output = ()> {
26341    let complete_from_marker: TextRangeMarker = '|'.into();
26342    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26343    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26344
26345    let (_, mut marked_ranges) = marked_text_ranges_by(
26346        marked_string,
26347        vec![
26348            complete_from_marker.clone(),
26349            replace_range_marker.clone(),
26350            insert_range_marker.clone(),
26351        ],
26352    );
26353
26354    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26355        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26356    ));
26357    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26358    let replace_range =
26359        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26360
26361    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26362        Some(ranges) if !ranges.is_empty() => {
26363            let range1 = ranges[0].clone();
26364            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26365        }
26366        _ => lsp::Range {
26367            start: replace_range.start,
26368            end: complete_from_position,
26369        },
26370    };
26371
26372    let mut request =
26373        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26374            let completions = completions.clone();
26375            counter.fetch_add(1, atomic::Ordering::Release);
26376            async move {
26377                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26378                assert_eq!(
26379                    params.text_document_position.position, complete_from_position,
26380                    "marker `|` position doesn't match",
26381                );
26382                Ok(Some(lsp::CompletionResponse::Array(
26383                    completions
26384                        .iter()
26385                        .map(|(label, new_text)| lsp::CompletionItem {
26386                            label: label.to_string(),
26387                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26388                                lsp::InsertReplaceEdit {
26389                                    insert: insert_range,
26390                                    replace: replace_range,
26391                                    new_text: new_text.to_string(),
26392                                },
26393                            )),
26394                            ..Default::default()
26395                        })
26396                        .collect(),
26397                )))
26398            }
26399        });
26400
26401    async move {
26402        request.next().await;
26403    }
26404}
26405
26406fn handle_resolve_completion_request(
26407    cx: &mut EditorLspTestContext,
26408    edits: Option<Vec<(&'static str, &'static str)>>,
26409) -> impl Future<Output = ()> {
26410    let edits = edits.map(|edits| {
26411        edits
26412            .iter()
26413            .map(|(marked_string, new_text)| {
26414                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26415                let replace_range = cx.to_lsp_range(
26416                    MultiBufferOffset(marked_ranges[0].start)
26417                        ..MultiBufferOffset(marked_ranges[0].end),
26418                );
26419                lsp::TextEdit::new(replace_range, new_text.to_string())
26420            })
26421            .collect::<Vec<_>>()
26422    });
26423
26424    let mut request =
26425        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26426            let edits = edits.clone();
26427            async move {
26428                Ok(lsp::CompletionItem {
26429                    additional_text_edits: edits,
26430                    ..Default::default()
26431                })
26432            }
26433        });
26434
26435    async move {
26436        request.next().await;
26437    }
26438}
26439
26440pub(crate) fn update_test_language_settings(
26441    cx: &mut TestAppContext,
26442    f: impl Fn(&mut AllLanguageSettingsContent),
26443) {
26444    cx.update(|cx| {
26445        SettingsStore::update_global(cx, |store, cx| {
26446            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26447        });
26448    });
26449}
26450
26451pub(crate) fn update_test_project_settings(
26452    cx: &mut TestAppContext,
26453    f: impl Fn(&mut ProjectSettingsContent),
26454) {
26455    cx.update(|cx| {
26456        SettingsStore::update_global(cx, |store, cx| {
26457            store.update_user_settings(cx, |settings| f(&mut settings.project));
26458        });
26459    });
26460}
26461
26462pub(crate) fn update_test_editor_settings(
26463    cx: &mut TestAppContext,
26464    f: impl Fn(&mut EditorSettingsContent),
26465) {
26466    cx.update(|cx| {
26467        SettingsStore::update_global(cx, |store, cx| {
26468            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26469        })
26470    })
26471}
26472
26473pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26474    cx.update(|cx| {
26475        assets::Assets.load_test_fonts(cx);
26476        let store = SettingsStore::test(cx);
26477        cx.set_global(store);
26478        theme::init(theme::LoadThemes::JustBase, cx);
26479        release_channel::init(semver::Version::new(0, 0, 0), cx);
26480        crate::init(cx);
26481    });
26482    zlog::init_test();
26483    update_test_language_settings(cx, f);
26484}
26485
26486#[track_caller]
26487fn assert_hunk_revert(
26488    not_reverted_text_with_selections: &str,
26489    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26490    expected_reverted_text_with_selections: &str,
26491    base_text: &str,
26492    cx: &mut EditorLspTestContext,
26493) {
26494    cx.set_state(not_reverted_text_with_selections);
26495    cx.set_head_text(base_text);
26496    cx.executor().run_until_parked();
26497
26498    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26499        let snapshot = editor.snapshot(window, cx);
26500        let reverted_hunk_statuses = snapshot
26501            .buffer_snapshot()
26502            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26503            .map(|hunk| hunk.status().kind)
26504            .collect::<Vec<_>>();
26505
26506        editor.git_restore(&Default::default(), window, cx);
26507        reverted_hunk_statuses
26508    });
26509    cx.executor().run_until_parked();
26510    cx.assert_editor_state(expected_reverted_text_with_selections);
26511    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26512}
26513
26514#[gpui::test(iterations = 10)]
26515async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26516    init_test(cx, |_| {});
26517
26518    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26519    let counter = diagnostic_requests.clone();
26520
26521    let fs = FakeFs::new(cx.executor());
26522    fs.insert_tree(
26523        path!("/a"),
26524        json!({
26525            "first.rs": "fn main() { let a = 5; }",
26526            "second.rs": "// Test file",
26527        }),
26528    )
26529    .await;
26530
26531    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26532    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26533    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26534
26535    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26536    language_registry.add(rust_lang());
26537    let mut fake_servers = language_registry.register_fake_lsp(
26538        "Rust",
26539        FakeLspAdapter {
26540            capabilities: lsp::ServerCapabilities {
26541                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26542                    lsp::DiagnosticOptions {
26543                        identifier: None,
26544                        inter_file_dependencies: true,
26545                        workspace_diagnostics: true,
26546                        work_done_progress_options: Default::default(),
26547                    },
26548                )),
26549                ..Default::default()
26550            },
26551            ..Default::default()
26552        },
26553    );
26554
26555    let editor = workspace
26556        .update(cx, |workspace, window, cx| {
26557            workspace.open_abs_path(
26558                PathBuf::from(path!("/a/first.rs")),
26559                OpenOptions::default(),
26560                window,
26561                cx,
26562            )
26563        })
26564        .unwrap()
26565        .await
26566        .unwrap()
26567        .downcast::<Editor>()
26568        .unwrap();
26569    let fake_server = fake_servers.next().await.unwrap();
26570    let server_id = fake_server.server.server_id();
26571    let mut first_request = fake_server
26572        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26573            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26574            let result_id = Some(new_result_id.to_string());
26575            assert_eq!(
26576                params.text_document.uri,
26577                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26578            );
26579            async move {
26580                Ok(lsp::DocumentDiagnosticReportResult::Report(
26581                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26582                        related_documents: None,
26583                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26584                            items: Vec::new(),
26585                            result_id,
26586                        },
26587                    }),
26588                ))
26589            }
26590        });
26591
26592    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
26593        project.update(cx, |project, cx| {
26594            let buffer_id = editor
26595                .read(cx)
26596                .buffer()
26597                .read(cx)
26598                .as_singleton()
26599                .expect("created a singleton buffer")
26600                .read(cx)
26601                .remote_id();
26602            let buffer_result_id = project
26603                .lsp_store()
26604                .read(cx)
26605                .result_id(server_id, buffer_id, cx);
26606            assert_eq!(expected, buffer_result_id);
26607        });
26608    };
26609
26610    ensure_result_id(None, cx);
26611    cx.executor().advance_clock(Duration::from_millis(60));
26612    cx.executor().run_until_parked();
26613    assert_eq!(
26614        diagnostic_requests.load(atomic::Ordering::Acquire),
26615        1,
26616        "Opening file should trigger diagnostic request"
26617    );
26618    first_request
26619        .next()
26620        .await
26621        .expect("should have sent the first diagnostics pull request");
26622    ensure_result_id(Some("1".to_string()), cx);
26623
26624    // Editing should trigger diagnostics
26625    editor.update_in(cx, |editor, window, cx| {
26626        editor.handle_input("2", window, cx)
26627    });
26628    cx.executor().advance_clock(Duration::from_millis(60));
26629    cx.executor().run_until_parked();
26630    assert_eq!(
26631        diagnostic_requests.load(atomic::Ordering::Acquire),
26632        2,
26633        "Editing should trigger diagnostic request"
26634    );
26635    ensure_result_id(Some("2".to_string()), cx);
26636
26637    // Moving cursor should not trigger diagnostic request
26638    editor.update_in(cx, |editor, window, cx| {
26639        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26640            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26641        });
26642    });
26643    cx.executor().advance_clock(Duration::from_millis(60));
26644    cx.executor().run_until_parked();
26645    assert_eq!(
26646        diagnostic_requests.load(atomic::Ordering::Acquire),
26647        2,
26648        "Cursor movement should not trigger diagnostic request"
26649    );
26650    ensure_result_id(Some("2".to_string()), cx);
26651    // Multiple rapid edits should be debounced
26652    for _ in 0..5 {
26653        editor.update_in(cx, |editor, window, cx| {
26654            editor.handle_input("x", window, cx)
26655        });
26656    }
26657    cx.executor().advance_clock(Duration::from_millis(60));
26658    cx.executor().run_until_parked();
26659
26660    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26661    assert!(
26662        final_requests <= 4,
26663        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26664    );
26665    ensure_result_id(Some(final_requests.to_string()), cx);
26666}
26667
26668#[gpui::test]
26669async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26670    // Regression test for issue #11671
26671    // Previously, adding a cursor after moving multiple cursors would reset
26672    // the cursor count instead of adding to the existing cursors.
26673    init_test(cx, |_| {});
26674    let mut cx = EditorTestContext::new(cx).await;
26675
26676    // Create a simple buffer with cursor at start
26677    cx.set_state(indoc! {"
26678        ˇaaaa
26679        bbbb
26680        cccc
26681        dddd
26682        eeee
26683        ffff
26684        gggg
26685        hhhh"});
26686
26687    // Add 2 cursors below (so we have 3 total)
26688    cx.update_editor(|editor, window, cx| {
26689        editor.add_selection_below(&Default::default(), window, cx);
26690        editor.add_selection_below(&Default::default(), window, cx);
26691    });
26692
26693    // Verify we have 3 cursors
26694    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26695    assert_eq!(
26696        initial_count, 3,
26697        "Should have 3 cursors after adding 2 below"
26698    );
26699
26700    // Move down one line
26701    cx.update_editor(|editor, window, cx| {
26702        editor.move_down(&MoveDown, window, cx);
26703    });
26704
26705    // Add another cursor below
26706    cx.update_editor(|editor, window, cx| {
26707        editor.add_selection_below(&Default::default(), window, cx);
26708    });
26709
26710    // Should now have 4 cursors (3 original + 1 new)
26711    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26712    assert_eq!(
26713        final_count, 4,
26714        "Should have 4 cursors after moving and adding another"
26715    );
26716}
26717
26718#[gpui::test]
26719async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26720    init_test(cx, |_| {});
26721
26722    let mut cx = EditorTestContext::new(cx).await;
26723
26724    cx.set_state(indoc!(
26725        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26726           Second line here"#
26727    ));
26728
26729    cx.update_editor(|editor, window, cx| {
26730        // Enable soft wrapping with a narrow width to force soft wrapping and
26731        // confirm that more than 2 rows are being displayed.
26732        editor.set_wrap_width(Some(100.0.into()), cx);
26733        assert!(editor.display_text(cx).lines().count() > 2);
26734
26735        editor.add_selection_below(
26736            &AddSelectionBelow {
26737                skip_soft_wrap: true,
26738            },
26739            window,
26740            cx,
26741        );
26742
26743        assert_eq!(
26744            display_ranges(editor, cx),
26745            &[
26746                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26747                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26748            ]
26749        );
26750
26751        editor.add_selection_above(
26752            &AddSelectionAbove {
26753                skip_soft_wrap: true,
26754            },
26755            window,
26756            cx,
26757        );
26758
26759        assert_eq!(
26760            display_ranges(editor, cx),
26761            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26762        );
26763
26764        editor.add_selection_below(
26765            &AddSelectionBelow {
26766                skip_soft_wrap: false,
26767            },
26768            window,
26769            cx,
26770        );
26771
26772        assert_eq!(
26773            display_ranges(editor, cx),
26774            &[
26775                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26776                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26777            ]
26778        );
26779
26780        editor.add_selection_above(
26781            &AddSelectionAbove {
26782                skip_soft_wrap: false,
26783            },
26784            window,
26785            cx,
26786        );
26787
26788        assert_eq!(
26789            display_ranges(editor, cx),
26790            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26791        );
26792    });
26793}
26794
26795#[gpui::test(iterations = 10)]
26796async fn test_document_colors(cx: &mut TestAppContext) {
26797    let expected_color = Rgba {
26798        r: 0.33,
26799        g: 0.33,
26800        b: 0.33,
26801        a: 0.33,
26802    };
26803
26804    init_test(cx, |_| {});
26805
26806    let fs = FakeFs::new(cx.executor());
26807    fs.insert_tree(
26808        path!("/a"),
26809        json!({
26810            "first.rs": "fn main() { let a = 5; }",
26811        }),
26812    )
26813    .await;
26814
26815    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26816    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26817    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26818
26819    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26820    language_registry.add(rust_lang());
26821    let mut fake_servers = language_registry.register_fake_lsp(
26822        "Rust",
26823        FakeLspAdapter {
26824            capabilities: lsp::ServerCapabilities {
26825                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26826                ..lsp::ServerCapabilities::default()
26827            },
26828            name: "rust-analyzer",
26829            ..FakeLspAdapter::default()
26830        },
26831    );
26832    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26833        "Rust",
26834        FakeLspAdapter {
26835            capabilities: lsp::ServerCapabilities {
26836                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26837                ..lsp::ServerCapabilities::default()
26838            },
26839            name: "not-rust-analyzer",
26840            ..FakeLspAdapter::default()
26841        },
26842    );
26843
26844    let editor = workspace
26845        .update(cx, |workspace, window, cx| {
26846            workspace.open_abs_path(
26847                PathBuf::from(path!("/a/first.rs")),
26848                OpenOptions::default(),
26849                window,
26850                cx,
26851            )
26852        })
26853        .unwrap()
26854        .await
26855        .unwrap()
26856        .downcast::<Editor>()
26857        .unwrap();
26858    let fake_language_server = fake_servers.next().await.unwrap();
26859    let fake_language_server_without_capabilities =
26860        fake_servers_without_capabilities.next().await.unwrap();
26861    let requests_made = Arc::new(AtomicUsize::new(0));
26862    let closure_requests_made = Arc::clone(&requests_made);
26863    let mut color_request_handle = fake_language_server
26864        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26865            let requests_made = Arc::clone(&closure_requests_made);
26866            async move {
26867                assert_eq!(
26868                    params.text_document.uri,
26869                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26870                );
26871                requests_made.fetch_add(1, atomic::Ordering::Release);
26872                Ok(vec![
26873                    lsp::ColorInformation {
26874                        range: lsp::Range {
26875                            start: lsp::Position {
26876                                line: 0,
26877                                character: 0,
26878                            },
26879                            end: lsp::Position {
26880                                line: 0,
26881                                character: 1,
26882                            },
26883                        },
26884                        color: lsp::Color {
26885                            red: 0.33,
26886                            green: 0.33,
26887                            blue: 0.33,
26888                            alpha: 0.33,
26889                        },
26890                    },
26891                    lsp::ColorInformation {
26892                        range: lsp::Range {
26893                            start: lsp::Position {
26894                                line: 0,
26895                                character: 0,
26896                            },
26897                            end: lsp::Position {
26898                                line: 0,
26899                                character: 1,
26900                            },
26901                        },
26902                        color: lsp::Color {
26903                            red: 0.33,
26904                            green: 0.33,
26905                            blue: 0.33,
26906                            alpha: 0.33,
26907                        },
26908                    },
26909                ])
26910            }
26911        });
26912
26913    let _handle = fake_language_server_without_capabilities
26914        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26915            panic!("Should not be called");
26916        });
26917    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26918    color_request_handle.next().await.unwrap();
26919    cx.run_until_parked();
26920    assert_eq!(
26921        1,
26922        requests_made.load(atomic::Ordering::Acquire),
26923        "Should query for colors once per editor open"
26924    );
26925    editor.update_in(cx, |editor, _, cx| {
26926        assert_eq!(
26927            vec![expected_color],
26928            extract_color_inlays(editor, cx),
26929            "Should have an initial inlay"
26930        );
26931    });
26932
26933    // opening another file in a split should not influence the LSP query counter
26934    workspace
26935        .update(cx, |workspace, window, cx| {
26936            assert_eq!(
26937                workspace.panes().len(),
26938                1,
26939                "Should have one pane with one editor"
26940            );
26941            workspace.move_item_to_pane_in_direction(
26942                &MoveItemToPaneInDirection {
26943                    direction: SplitDirection::Right,
26944                    focus: false,
26945                    clone: true,
26946                },
26947                window,
26948                cx,
26949            );
26950        })
26951        .unwrap();
26952    cx.run_until_parked();
26953    workspace
26954        .update(cx, |workspace, _, cx| {
26955            let panes = workspace.panes();
26956            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26957            for pane in panes {
26958                let editor = pane
26959                    .read(cx)
26960                    .active_item()
26961                    .and_then(|item| item.downcast::<Editor>())
26962                    .expect("Should have opened an editor in each split");
26963                let editor_file = editor
26964                    .read(cx)
26965                    .buffer()
26966                    .read(cx)
26967                    .as_singleton()
26968                    .expect("test deals with singleton buffers")
26969                    .read(cx)
26970                    .file()
26971                    .expect("test buffese should have a file")
26972                    .path();
26973                assert_eq!(
26974                    editor_file.as_ref(),
26975                    rel_path("first.rs"),
26976                    "Both editors should be opened for the same file"
26977                )
26978            }
26979        })
26980        .unwrap();
26981
26982    cx.executor().advance_clock(Duration::from_millis(500));
26983    let save = editor.update_in(cx, |editor, window, cx| {
26984        editor.move_to_end(&MoveToEnd, window, cx);
26985        editor.handle_input("dirty", window, cx);
26986        editor.save(
26987            SaveOptions {
26988                format: true,
26989                autosave: true,
26990            },
26991            project.clone(),
26992            window,
26993            cx,
26994        )
26995    });
26996    save.await.unwrap();
26997
26998    color_request_handle.next().await.unwrap();
26999    cx.run_until_parked();
27000    assert_eq!(
27001        2,
27002        requests_made.load(atomic::Ordering::Acquire),
27003        "Should query for colors once per save (deduplicated) and once per formatting after save"
27004    );
27005
27006    drop(editor);
27007    let close = workspace
27008        .update(cx, |workspace, window, cx| {
27009            workspace.active_pane().update(cx, |pane, cx| {
27010                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27011            })
27012        })
27013        .unwrap();
27014    close.await.unwrap();
27015    let close = workspace
27016        .update(cx, |workspace, window, cx| {
27017            workspace.active_pane().update(cx, |pane, cx| {
27018                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27019            })
27020        })
27021        .unwrap();
27022    close.await.unwrap();
27023    assert_eq!(
27024        2,
27025        requests_made.load(atomic::Ordering::Acquire),
27026        "After saving and closing all editors, no extra requests should be made"
27027    );
27028    workspace
27029        .update(cx, |workspace, _, cx| {
27030            assert!(
27031                workspace.active_item(cx).is_none(),
27032                "Should close all editors"
27033            )
27034        })
27035        .unwrap();
27036
27037    workspace
27038        .update(cx, |workspace, window, cx| {
27039            workspace.active_pane().update(cx, |pane, cx| {
27040                pane.navigate_backward(&workspace::GoBack, window, cx);
27041            })
27042        })
27043        .unwrap();
27044    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27045    cx.run_until_parked();
27046    let editor = workspace
27047        .update(cx, |workspace, _, cx| {
27048            workspace
27049                .active_item(cx)
27050                .expect("Should have reopened the editor again after navigating back")
27051                .downcast::<Editor>()
27052                .expect("Should be an editor")
27053        })
27054        .unwrap();
27055
27056    assert_eq!(
27057        2,
27058        requests_made.load(atomic::Ordering::Acquire),
27059        "Cache should be reused on buffer close and reopen"
27060    );
27061    editor.update(cx, |editor, cx| {
27062        assert_eq!(
27063            vec![expected_color],
27064            extract_color_inlays(editor, cx),
27065            "Should have an initial inlay"
27066        );
27067    });
27068
27069    drop(color_request_handle);
27070    let closure_requests_made = Arc::clone(&requests_made);
27071    let mut empty_color_request_handle = fake_language_server
27072        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27073            let requests_made = Arc::clone(&closure_requests_made);
27074            async move {
27075                assert_eq!(
27076                    params.text_document.uri,
27077                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27078                );
27079                requests_made.fetch_add(1, atomic::Ordering::Release);
27080                Ok(Vec::new())
27081            }
27082        });
27083    let save = editor.update_in(cx, |editor, window, cx| {
27084        editor.move_to_end(&MoveToEnd, window, cx);
27085        editor.handle_input("dirty_again", window, cx);
27086        editor.save(
27087            SaveOptions {
27088                format: false,
27089                autosave: true,
27090            },
27091            project.clone(),
27092            window,
27093            cx,
27094        )
27095    });
27096    save.await.unwrap();
27097
27098    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27099    empty_color_request_handle.next().await.unwrap();
27100    cx.run_until_parked();
27101    assert_eq!(
27102        3,
27103        requests_made.load(atomic::Ordering::Acquire),
27104        "Should query for colors once per save only, as formatting was not requested"
27105    );
27106    editor.update(cx, |editor, cx| {
27107        assert_eq!(
27108            Vec::<Rgba>::new(),
27109            extract_color_inlays(editor, cx),
27110            "Should clear all colors when the server returns an empty response"
27111        );
27112    });
27113}
27114
27115#[gpui::test]
27116async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27117    init_test(cx, |_| {});
27118    let (editor, cx) = cx.add_window_view(Editor::single_line);
27119    editor.update_in(cx, |editor, window, cx| {
27120        editor.set_text("oops\n\nwow\n", window, cx)
27121    });
27122    cx.run_until_parked();
27123    editor.update(cx, |editor, cx| {
27124        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27125    });
27126    editor.update(cx, |editor, cx| {
27127        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27128    });
27129    cx.run_until_parked();
27130    editor.update(cx, |editor, cx| {
27131        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27132    });
27133}
27134
27135#[gpui::test]
27136async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27137    init_test(cx, |_| {});
27138
27139    cx.update(|cx| {
27140        register_project_item::<Editor>(cx);
27141    });
27142
27143    let fs = FakeFs::new(cx.executor());
27144    fs.insert_tree("/root1", json!({})).await;
27145    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27146        .await;
27147
27148    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27149    let (workspace, cx) =
27150        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27151
27152    let worktree_id = project.update(cx, |project, cx| {
27153        project.worktrees(cx).next().unwrap().read(cx).id()
27154    });
27155
27156    let handle = workspace
27157        .update_in(cx, |workspace, window, cx| {
27158            let project_path = (worktree_id, rel_path("one.pdf"));
27159            workspace.open_path(project_path, None, true, window, cx)
27160        })
27161        .await
27162        .unwrap();
27163
27164    assert_eq!(
27165        handle.to_any_view().entity_type(),
27166        TypeId::of::<InvalidItemView>()
27167    );
27168}
27169
27170#[gpui::test]
27171async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27172    init_test(cx, |_| {});
27173
27174    let language = Arc::new(Language::new(
27175        LanguageConfig::default(),
27176        Some(tree_sitter_rust::LANGUAGE.into()),
27177    ));
27178
27179    // Test hierarchical sibling navigation
27180    let text = r#"
27181        fn outer() {
27182            if condition {
27183                let a = 1;
27184            }
27185            let b = 2;
27186        }
27187
27188        fn another() {
27189            let c = 3;
27190        }
27191    "#;
27192
27193    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27194    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27195    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27196
27197    // Wait for parsing to complete
27198    editor
27199        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27200        .await;
27201
27202    editor.update_in(cx, |editor, window, cx| {
27203        // Start by selecting "let a = 1;" inside the if block
27204        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27205            s.select_display_ranges([
27206                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27207            ]);
27208        });
27209
27210        let initial_selection = editor
27211            .selections
27212            .display_ranges(&editor.display_snapshot(cx));
27213        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27214
27215        // Test select next sibling - should move up levels to find the next sibling
27216        // Since "let a = 1;" has no siblings in the if block, it should move up
27217        // to find "let b = 2;" which is a sibling of the if block
27218        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27219        let next_selection = editor
27220            .selections
27221            .display_ranges(&editor.display_snapshot(cx));
27222
27223        // Should have a selection and it should be different from the initial
27224        assert_eq!(
27225            next_selection.len(),
27226            1,
27227            "Should have one selection after next"
27228        );
27229        assert_ne!(
27230            next_selection[0], initial_selection[0],
27231            "Next sibling selection should be different"
27232        );
27233
27234        // Test hierarchical navigation by going to the end of the current function
27235        // and trying to navigate to the next function
27236        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27237            s.select_display_ranges([
27238                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27239            ]);
27240        });
27241
27242        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27243        let function_next_selection = editor
27244            .selections
27245            .display_ranges(&editor.display_snapshot(cx));
27246
27247        // Should move to the next function
27248        assert_eq!(
27249            function_next_selection.len(),
27250            1,
27251            "Should have one selection after function next"
27252        );
27253
27254        // Test select previous sibling navigation
27255        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27256        let prev_selection = editor
27257            .selections
27258            .display_ranges(&editor.display_snapshot(cx));
27259
27260        // Should have a selection and it should be different
27261        assert_eq!(
27262            prev_selection.len(),
27263            1,
27264            "Should have one selection after prev"
27265        );
27266        assert_ne!(
27267            prev_selection[0], function_next_selection[0],
27268            "Previous sibling selection should be different from next"
27269        );
27270    });
27271}
27272
27273#[gpui::test]
27274async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27275    init_test(cx, |_| {});
27276
27277    let mut cx = EditorTestContext::new(cx).await;
27278    cx.set_state(
27279        "let ˇvariable = 42;
27280let another = variable + 1;
27281let result = variable * 2;",
27282    );
27283
27284    // Set up document highlights manually (simulating LSP response)
27285    cx.update_editor(|editor, _window, cx| {
27286        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27287
27288        // Create highlights for "variable" occurrences
27289        let highlight_ranges = [
27290            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27291            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27292            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27293        ];
27294
27295        let anchor_ranges: Vec<_> = highlight_ranges
27296            .iter()
27297            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27298            .collect();
27299
27300        editor.highlight_background::<DocumentHighlightRead>(
27301            &anchor_ranges,
27302            |theme| theme.colors().editor_document_highlight_read_background,
27303            cx,
27304        );
27305    });
27306
27307    // Go to next highlight - should move to second "variable"
27308    cx.update_editor(|editor, window, cx| {
27309        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27310    });
27311    cx.assert_editor_state(
27312        "let variable = 42;
27313let another = ˇvariable + 1;
27314let result = variable * 2;",
27315    );
27316
27317    // Go to next highlight - should move to third "variable"
27318    cx.update_editor(|editor, window, cx| {
27319        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27320    });
27321    cx.assert_editor_state(
27322        "let variable = 42;
27323let another = variable + 1;
27324let result = ˇvariable * 2;",
27325    );
27326
27327    // Go to next highlight - should stay at third "variable" (no wrap-around)
27328    cx.update_editor(|editor, window, cx| {
27329        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27330    });
27331    cx.assert_editor_state(
27332        "let variable = 42;
27333let another = variable + 1;
27334let result = ˇvariable * 2;",
27335    );
27336
27337    // Now test going backwards from third position
27338    cx.update_editor(|editor, window, cx| {
27339        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27340    });
27341    cx.assert_editor_state(
27342        "let variable = 42;
27343let another = ˇvariable + 1;
27344let result = variable * 2;",
27345    );
27346
27347    // Go to previous highlight - should move to first "variable"
27348    cx.update_editor(|editor, window, cx| {
27349        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27350    });
27351    cx.assert_editor_state(
27352        "let ˇvariable = 42;
27353let another = variable + 1;
27354let result = variable * 2;",
27355    );
27356
27357    // Go to previous highlight - should stay on first "variable"
27358    cx.update_editor(|editor, window, cx| {
27359        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27360    });
27361    cx.assert_editor_state(
27362        "let ˇvariable = 42;
27363let another = variable + 1;
27364let result = variable * 2;",
27365    );
27366}
27367
27368#[gpui::test]
27369async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27370    cx: &mut gpui::TestAppContext,
27371) {
27372    init_test(cx, |_| {});
27373
27374    let url = "https://zed.dev";
27375
27376    let markdown_language = Arc::new(Language::new(
27377        LanguageConfig {
27378            name: "Markdown".into(),
27379            ..LanguageConfig::default()
27380        },
27381        None,
27382    ));
27383
27384    let mut cx = EditorTestContext::new(cx).await;
27385    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27386    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27387
27388    cx.update_editor(|editor, window, cx| {
27389        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27390        editor.paste(&Paste, window, cx);
27391    });
27392
27393    cx.assert_editor_state(&format!(
27394        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27395    ));
27396}
27397
27398#[gpui::test]
27399async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27400    cx: &mut gpui::TestAppContext,
27401) {
27402    init_test(cx, |_| {});
27403
27404    let url = "https://zed.dev";
27405
27406    let markdown_language = Arc::new(Language::new(
27407        LanguageConfig {
27408            name: "Markdown".into(),
27409            ..LanguageConfig::default()
27410        },
27411        None,
27412    ));
27413
27414    let mut cx = EditorTestContext::new(cx).await;
27415    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27416    cx.set_state(&format!(
27417        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27418    ));
27419
27420    cx.update_editor(|editor, window, cx| {
27421        editor.copy(&Copy, window, cx);
27422    });
27423
27424    cx.set_state(&format!(
27425        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27426    ));
27427
27428    cx.update_editor(|editor, window, cx| {
27429        editor.paste(&Paste, window, cx);
27430    });
27431
27432    cx.assert_editor_state(&format!(
27433        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27434    ));
27435}
27436
27437#[gpui::test]
27438async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27439    cx: &mut gpui::TestAppContext,
27440) {
27441    init_test(cx, |_| {});
27442
27443    let url = "https://zed.dev";
27444
27445    let markdown_language = Arc::new(Language::new(
27446        LanguageConfig {
27447            name: "Markdown".into(),
27448            ..LanguageConfig::default()
27449        },
27450        None,
27451    ));
27452
27453    let mut cx = EditorTestContext::new(cx).await;
27454    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27455    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27456
27457    cx.update_editor(|editor, window, cx| {
27458        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27459        editor.paste(&Paste, window, cx);
27460    });
27461
27462    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27463}
27464
27465#[gpui::test]
27466async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27467    cx: &mut gpui::TestAppContext,
27468) {
27469    init_test(cx, |_| {});
27470
27471    let text = "Awesome";
27472
27473    let markdown_language = Arc::new(Language::new(
27474        LanguageConfig {
27475            name: "Markdown".into(),
27476            ..LanguageConfig::default()
27477        },
27478        None,
27479    ));
27480
27481    let mut cx = EditorTestContext::new(cx).await;
27482    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27483    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27484
27485    cx.update_editor(|editor, window, cx| {
27486        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27487        editor.paste(&Paste, window, cx);
27488    });
27489
27490    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27491}
27492
27493#[gpui::test]
27494async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27495    cx: &mut gpui::TestAppContext,
27496) {
27497    init_test(cx, |_| {});
27498
27499    let url = "https://zed.dev";
27500
27501    let markdown_language = Arc::new(Language::new(
27502        LanguageConfig {
27503            name: "Rust".into(),
27504            ..LanguageConfig::default()
27505        },
27506        None,
27507    ));
27508
27509    let mut cx = EditorTestContext::new(cx).await;
27510    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27511    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27512
27513    cx.update_editor(|editor, window, cx| {
27514        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27515        editor.paste(&Paste, window, cx);
27516    });
27517
27518    cx.assert_editor_state(&format!(
27519        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27520    ));
27521}
27522
27523#[gpui::test]
27524async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27525    cx: &mut TestAppContext,
27526) {
27527    init_test(cx, |_| {});
27528
27529    let url = "https://zed.dev";
27530
27531    let markdown_language = Arc::new(Language::new(
27532        LanguageConfig {
27533            name: "Markdown".into(),
27534            ..LanguageConfig::default()
27535        },
27536        None,
27537    ));
27538
27539    let (editor, cx) = cx.add_window_view(|window, cx| {
27540        let multi_buffer = MultiBuffer::build_multi(
27541            [
27542                ("this will embed -> link", vec![Point::row_range(0..1)]),
27543                ("this will replace -> link", vec![Point::row_range(0..1)]),
27544            ],
27545            cx,
27546        );
27547        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
27548        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27549            s.select_ranges(vec![
27550                Point::new(0, 19)..Point::new(0, 23),
27551                Point::new(1, 21)..Point::new(1, 25),
27552            ])
27553        });
27554        let first_buffer_id = multi_buffer
27555            .read(cx)
27556            .excerpt_buffer_ids()
27557            .into_iter()
27558            .next()
27559            .unwrap();
27560        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
27561        first_buffer.update(cx, |buffer, cx| {
27562            buffer.set_language(Some(markdown_language.clone()), cx);
27563        });
27564
27565        editor
27566    });
27567    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27568
27569    cx.update_editor(|editor, window, cx| {
27570        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27571        editor.paste(&Paste, window, cx);
27572    });
27573
27574    cx.assert_editor_state(&format!(
27575        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
27576    ));
27577}
27578
27579#[gpui::test]
27580async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
27581    init_test(cx, |_| {});
27582
27583    let fs = FakeFs::new(cx.executor());
27584    fs.insert_tree(
27585        path!("/project"),
27586        json!({
27587            "first.rs": "# First Document\nSome content here.",
27588            "second.rs": "Plain text content for second file.",
27589        }),
27590    )
27591    .await;
27592
27593    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
27594    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27595    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27596
27597    let language = rust_lang();
27598    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27599    language_registry.add(language.clone());
27600    let mut fake_servers = language_registry.register_fake_lsp(
27601        "Rust",
27602        FakeLspAdapter {
27603            ..FakeLspAdapter::default()
27604        },
27605    );
27606
27607    let buffer1 = project
27608        .update(cx, |project, cx| {
27609            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
27610        })
27611        .await
27612        .unwrap();
27613    let buffer2 = project
27614        .update(cx, |project, cx| {
27615            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
27616        })
27617        .await
27618        .unwrap();
27619
27620    let multi_buffer = cx.new(|cx| {
27621        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
27622        multi_buffer.set_excerpts_for_path(
27623            PathKey::for_buffer(&buffer1, cx),
27624            buffer1.clone(),
27625            [Point::zero()..buffer1.read(cx).max_point()],
27626            3,
27627            cx,
27628        );
27629        multi_buffer.set_excerpts_for_path(
27630            PathKey::for_buffer(&buffer2, cx),
27631            buffer2.clone(),
27632            [Point::zero()..buffer1.read(cx).max_point()],
27633            3,
27634            cx,
27635        );
27636        multi_buffer
27637    });
27638
27639    let (editor, cx) = cx.add_window_view(|window, cx| {
27640        Editor::new(
27641            EditorMode::full(),
27642            multi_buffer,
27643            Some(project.clone()),
27644            window,
27645            cx,
27646        )
27647    });
27648
27649    let fake_language_server = fake_servers.next().await.unwrap();
27650
27651    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
27652
27653    let save = editor.update_in(cx, |editor, window, cx| {
27654        assert!(editor.is_dirty(cx));
27655
27656        editor.save(
27657            SaveOptions {
27658                format: true,
27659                autosave: true,
27660            },
27661            project,
27662            window,
27663            cx,
27664        )
27665    });
27666    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27667    let (done_edit_tx, done_edit_rx) = oneshot::channel();
27668    let mut done_edit_rx = Some(done_edit_rx);
27669    let mut start_edit_tx = Some(start_edit_tx);
27670
27671    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
27672        start_edit_tx.take().unwrap().send(()).unwrap();
27673        let done_edit_rx = done_edit_rx.take().unwrap();
27674        async move {
27675            done_edit_rx.await.unwrap();
27676            Ok(None)
27677        }
27678    });
27679
27680    start_edit_rx.await.unwrap();
27681    buffer2
27682        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
27683        .unwrap();
27684
27685    done_edit_tx.send(()).unwrap();
27686
27687    save.await.unwrap();
27688    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
27689}
27690
27691#[track_caller]
27692fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
27693    editor
27694        .all_inlays(cx)
27695        .into_iter()
27696        .filter_map(|inlay| inlay.get_color())
27697        .map(Rgba::from)
27698        .collect()
27699}
27700
27701#[gpui::test]
27702fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
27703    init_test(cx, |_| {});
27704
27705    let editor = cx.add_window(|window, cx| {
27706        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
27707        build_editor(buffer, window, cx)
27708    });
27709
27710    editor
27711        .update(cx, |editor, window, cx| {
27712            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27713                s.select_display_ranges([
27714                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
27715                ])
27716            });
27717
27718            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
27719
27720            assert_eq!(
27721                editor.display_text(cx),
27722                "line1\nline2\nline2",
27723                "Duplicating last line upward should create duplicate above, not on same line"
27724            );
27725
27726            assert_eq!(
27727                editor
27728                    .selections
27729                    .display_ranges(&editor.display_snapshot(cx)),
27730                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27731                "Selection should move to the duplicated line"
27732            );
27733        })
27734        .unwrap();
27735}
27736
27737#[gpui::test]
27738async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27739    init_test(cx, |_| {});
27740
27741    let mut cx = EditorTestContext::new(cx).await;
27742
27743    cx.set_state("line1\nline2ˇ");
27744
27745    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27746
27747    let clipboard_text = cx
27748        .read_from_clipboard()
27749        .and_then(|item| item.text().as_deref().map(str::to_string));
27750
27751    assert_eq!(
27752        clipboard_text,
27753        Some("line2\n".to_string()),
27754        "Copying a line without trailing newline should include a newline"
27755    );
27756
27757    cx.set_state("line1\nˇ");
27758
27759    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27760
27761    cx.assert_editor_state("line1\nline2\nˇ");
27762}
27763
27764#[gpui::test]
27765async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27766    init_test(cx, |_| {});
27767
27768    let mut cx = EditorTestContext::new(cx).await;
27769
27770    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27771
27772    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27773
27774    let clipboard_text = cx
27775        .read_from_clipboard()
27776        .and_then(|item| item.text().as_deref().map(str::to_string));
27777
27778    assert_eq!(
27779        clipboard_text,
27780        Some("line1\nline2\nline3\n".to_string()),
27781        "Copying multiple lines should include a single newline between lines"
27782    );
27783
27784    cx.set_state("lineA\nˇ");
27785
27786    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27787
27788    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27789}
27790
27791#[gpui::test]
27792async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27793    init_test(cx, |_| {});
27794
27795    let mut cx = EditorTestContext::new(cx).await;
27796
27797    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27798
27799    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
27800
27801    let clipboard_text = cx
27802        .read_from_clipboard()
27803        .and_then(|item| item.text().as_deref().map(str::to_string));
27804
27805    assert_eq!(
27806        clipboard_text,
27807        Some("line1\nline2\nline3\n".to_string()),
27808        "Copying multiple lines should include a single newline between lines"
27809    );
27810
27811    cx.set_state("lineA\nˇ");
27812
27813    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27814
27815    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27816}
27817
27818#[gpui::test]
27819async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27820    init_test(cx, |_| {});
27821
27822    let mut cx = EditorTestContext::new(cx).await;
27823
27824    cx.set_state("line1\nline2ˇ");
27825    cx.update_editor(|e, window, cx| {
27826        e.set_mode(EditorMode::SingleLine);
27827        assert!(e.key_context(window, cx).contains("end_of_input"));
27828    });
27829    cx.set_state("ˇline1\nline2");
27830    cx.update_editor(|e, window, cx| {
27831        assert!(!e.key_context(window, cx).contains("end_of_input"));
27832    });
27833    cx.set_state("line1ˇ\nline2");
27834    cx.update_editor(|e, window, cx| {
27835        assert!(!e.key_context(window, cx).contains("end_of_input"));
27836    });
27837}
27838
27839#[gpui::test]
27840async fn test_sticky_scroll(cx: &mut TestAppContext) {
27841    init_test(cx, |_| {});
27842    let mut cx = EditorTestContext::new(cx).await;
27843
27844    let buffer = indoc! {"
27845            ˇfn foo() {
27846                let abc = 123;
27847            }
27848            struct Bar;
27849            impl Bar {
27850                fn new() -> Self {
27851                    Self
27852                }
27853            }
27854            fn baz() {
27855            }
27856        "};
27857    cx.set_state(&buffer);
27858
27859    cx.update_editor(|e, _, cx| {
27860        e.buffer()
27861            .read(cx)
27862            .as_singleton()
27863            .unwrap()
27864            .update(cx, |buffer, cx| {
27865                buffer.set_language(Some(rust_lang()), cx);
27866            })
27867    });
27868
27869    let mut sticky_headers = |offset: ScrollOffset| {
27870        cx.update_editor(|e, window, cx| {
27871            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27872            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27873                .into_iter()
27874                .map(
27875                    |StickyHeader {
27876                         start_point,
27877                         offset,
27878                         ..
27879                     }| { (start_point, offset) },
27880                )
27881                .collect::<Vec<_>>()
27882        })
27883    };
27884
27885    let fn_foo = Point { row: 0, column: 0 };
27886    let impl_bar = Point { row: 4, column: 0 };
27887    let fn_new = Point { row: 5, column: 4 };
27888
27889    assert_eq!(sticky_headers(0.0), vec![]);
27890    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27891    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27892    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27893    assert_eq!(sticky_headers(2.0), vec![]);
27894    assert_eq!(sticky_headers(2.5), vec![]);
27895    assert_eq!(sticky_headers(3.0), vec![]);
27896    assert_eq!(sticky_headers(3.5), vec![]);
27897    assert_eq!(sticky_headers(4.0), vec![]);
27898    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27899    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27900    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27901    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27902    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27903    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27904    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27905    assert_eq!(sticky_headers(8.0), vec![]);
27906    assert_eq!(sticky_headers(8.5), vec![]);
27907    assert_eq!(sticky_headers(9.0), vec![]);
27908    assert_eq!(sticky_headers(9.5), vec![]);
27909    assert_eq!(sticky_headers(10.0), vec![]);
27910}
27911
27912#[gpui::test]
27913async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27914    init_test(cx, |_| {});
27915    cx.update(|cx| {
27916        SettingsStore::update_global(cx, |store, cx| {
27917            store.update_user_settings(cx, |settings| {
27918                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27919                    enabled: Some(true),
27920                })
27921            });
27922        });
27923    });
27924    let mut cx = EditorTestContext::new(cx).await;
27925
27926    let line_height = cx.editor(|editor, window, _cx| {
27927        editor
27928            .style()
27929            .unwrap()
27930            .text
27931            .line_height_in_pixels(window.rem_size())
27932    });
27933
27934    let buffer = indoc! {"
27935            ˇfn foo() {
27936                let abc = 123;
27937            }
27938            struct Bar;
27939            impl Bar {
27940                fn new() -> Self {
27941                    Self
27942                }
27943            }
27944            fn baz() {
27945            }
27946        "};
27947    cx.set_state(&buffer);
27948
27949    cx.update_editor(|e, _, cx| {
27950        e.buffer()
27951            .read(cx)
27952            .as_singleton()
27953            .unwrap()
27954            .update(cx, |buffer, cx| {
27955                buffer.set_language(Some(rust_lang()), cx);
27956            })
27957    });
27958
27959    let fn_foo = || empty_range(0, 0);
27960    let impl_bar = || empty_range(4, 0);
27961    let fn_new = || empty_range(5, 4);
27962
27963    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27964        cx.update_editor(|e, window, cx| {
27965            e.scroll(
27966                gpui::Point {
27967                    x: 0.,
27968                    y: scroll_offset,
27969                },
27970                None,
27971                window,
27972                cx,
27973            );
27974        });
27975        cx.simulate_click(
27976            gpui::Point {
27977                x: px(0.),
27978                y: click_offset as f32 * line_height,
27979            },
27980            Modifiers::none(),
27981        );
27982        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27983    };
27984
27985    assert_eq!(
27986        scroll_and_click(
27987            4.5, // impl Bar is halfway off the screen
27988            0.0  // click top of screen
27989        ),
27990        // scrolled to impl Bar
27991        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27992    );
27993
27994    assert_eq!(
27995        scroll_and_click(
27996            4.5,  // impl Bar is halfway off the screen
27997            0.25  // click middle of impl Bar
27998        ),
27999        // scrolled to impl Bar
28000        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28001    );
28002
28003    assert_eq!(
28004        scroll_and_click(
28005            4.5, // impl Bar is halfway off the screen
28006            1.5  // click below impl Bar (e.g. fn new())
28007        ),
28008        // scrolled to fn new() - this is below the impl Bar header which has persisted
28009        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28010    );
28011
28012    assert_eq!(
28013        scroll_and_click(
28014            5.5,  // fn new is halfway underneath impl Bar
28015            0.75  // click on the overlap of impl Bar and fn new()
28016        ),
28017        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28018    );
28019
28020    assert_eq!(
28021        scroll_and_click(
28022            5.5,  // fn new is halfway underneath impl Bar
28023            1.25  // click on the visible part of fn new()
28024        ),
28025        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28026    );
28027
28028    assert_eq!(
28029        scroll_and_click(
28030            1.5, // fn foo is halfway off the screen
28031            0.0  // click top of screen
28032        ),
28033        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28034    );
28035
28036    assert_eq!(
28037        scroll_and_click(
28038            1.5,  // fn foo is halfway off the screen
28039            0.75  // click visible part of let abc...
28040        )
28041        .0,
28042        // no change in scroll
28043        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28044        (gpui::Point { x: 0., y: 1.5 })
28045    );
28046}
28047
28048#[gpui::test]
28049async fn test_next_prev_reference(cx: &mut TestAppContext) {
28050    const CYCLE_POSITIONS: &[&'static str] = &[
28051        indoc! {"
28052            fn foo() {
28053                let ˇabc = 123;
28054                let x = abc + 1;
28055                let y = abc + 2;
28056                let z = abc + 2;
28057            }
28058        "},
28059        indoc! {"
28060            fn foo() {
28061                let abc = 123;
28062                let x = ˇabc + 1;
28063                let y = abc + 2;
28064                let z = abc + 2;
28065            }
28066        "},
28067        indoc! {"
28068            fn foo() {
28069                let abc = 123;
28070                let x = abc + 1;
28071                let y = ˇabc + 2;
28072                let z = abc + 2;
28073            }
28074        "},
28075        indoc! {"
28076            fn foo() {
28077                let abc = 123;
28078                let x = abc + 1;
28079                let y = abc + 2;
28080                let z = ˇabc + 2;
28081            }
28082        "},
28083    ];
28084
28085    init_test(cx, |_| {});
28086
28087    let mut cx = EditorLspTestContext::new_rust(
28088        lsp::ServerCapabilities {
28089            references_provider: Some(lsp::OneOf::Left(true)),
28090            ..Default::default()
28091        },
28092        cx,
28093    )
28094    .await;
28095
28096    // importantly, the cursor is in the middle
28097    cx.set_state(indoc! {"
28098        fn foo() {
28099            let aˇbc = 123;
28100            let x = abc + 1;
28101            let y = abc + 2;
28102            let z = abc + 2;
28103        }
28104    "});
28105
28106    let reference_ranges = [
28107        lsp::Position::new(1, 8),
28108        lsp::Position::new(2, 12),
28109        lsp::Position::new(3, 12),
28110        lsp::Position::new(4, 12),
28111    ]
28112    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28113
28114    cx.lsp
28115        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28116            Ok(Some(
28117                reference_ranges
28118                    .map(|range| lsp::Location {
28119                        uri: params.text_document_position.text_document.uri.clone(),
28120                        range,
28121                    })
28122                    .to_vec(),
28123            ))
28124        });
28125
28126    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28127        cx.update_editor(|editor, window, cx| {
28128            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28129        })
28130        .unwrap()
28131        .await
28132        .unwrap()
28133    };
28134
28135    _move(Direction::Next, 1, &mut cx).await;
28136    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28137
28138    _move(Direction::Next, 1, &mut cx).await;
28139    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28140
28141    _move(Direction::Next, 1, &mut cx).await;
28142    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28143
28144    // loops back to the start
28145    _move(Direction::Next, 1, &mut cx).await;
28146    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28147
28148    // loops back to the end
28149    _move(Direction::Prev, 1, &mut cx).await;
28150    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28151
28152    _move(Direction::Prev, 1, &mut cx).await;
28153    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28154
28155    _move(Direction::Prev, 1, &mut cx).await;
28156    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28157
28158    _move(Direction::Prev, 1, &mut cx).await;
28159    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28160
28161    _move(Direction::Next, 3, &mut cx).await;
28162    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28163
28164    _move(Direction::Prev, 2, &mut cx).await;
28165    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28166}
28167
28168#[gpui::test]
28169async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
28170    init_test(cx, |_| {});
28171
28172    let (editor, cx) = cx.add_window_view(|window, cx| {
28173        let multi_buffer = MultiBuffer::build_multi(
28174            [
28175                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28176                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28177            ],
28178            cx,
28179        );
28180        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28181    });
28182
28183    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28184    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28185
28186    cx.assert_excerpts_with_selections(indoc! {"
28187        [EXCERPT]
28188        ˇ1
28189        2
28190        3
28191        [EXCERPT]
28192        1
28193        2
28194        3
28195        "});
28196
28197    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28198    cx.update_editor(|editor, window, cx| {
28199        editor.change_selections(None.into(), window, cx, |s| {
28200            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28201        });
28202    });
28203    cx.assert_excerpts_with_selections(indoc! {"
28204        [EXCERPT]
28205        1
2820628207        3
28208        [EXCERPT]
28209        1
28210        2
28211        3
28212        "});
28213
28214    cx.update_editor(|editor, window, cx| {
28215        editor
28216            .select_all_matches(&SelectAllMatches, window, cx)
28217            .unwrap();
28218    });
28219    cx.assert_excerpts_with_selections(indoc! {"
28220        [EXCERPT]
28221        1
2822228223        3
28224        [EXCERPT]
28225        1
2822628227        3
28228        "});
28229
28230    cx.update_editor(|editor, window, cx| {
28231        editor.handle_input("X", window, cx);
28232    });
28233    cx.assert_excerpts_with_selections(indoc! {"
28234        [EXCERPT]
28235        1
2823628237        3
28238        [EXCERPT]
28239        1
2824028241        3
28242        "});
28243
28244    // Scenario 2: Select "2", then fold second buffer before insertion
28245    cx.update_multibuffer(|mb, cx| {
28246        for buffer_id in buffer_ids.iter() {
28247            let buffer = mb.buffer(*buffer_id).unwrap();
28248            buffer.update(cx, |buffer, cx| {
28249                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28250            });
28251        }
28252    });
28253
28254    // Select "2" and select all matches
28255    cx.update_editor(|editor, window, cx| {
28256        editor.change_selections(None.into(), window, cx, |s| {
28257            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28258        });
28259        editor
28260            .select_all_matches(&SelectAllMatches, window, cx)
28261            .unwrap();
28262    });
28263
28264    // Fold second buffer - should remove selections from folded buffer
28265    cx.update_editor(|editor, _, cx| {
28266        editor.fold_buffer(buffer_ids[1], cx);
28267    });
28268    cx.assert_excerpts_with_selections(indoc! {"
28269        [EXCERPT]
28270        1
2827128272        3
28273        [EXCERPT]
28274        [FOLDED]
28275        "});
28276
28277    // Insert text - should only affect first buffer
28278    cx.update_editor(|editor, window, cx| {
28279        editor.handle_input("Y", window, cx);
28280    });
28281    cx.update_editor(|editor, _, cx| {
28282        editor.unfold_buffer(buffer_ids[1], cx);
28283    });
28284    cx.assert_excerpts_with_selections(indoc! {"
28285        [EXCERPT]
28286        1
2828728288        3
28289        [EXCERPT]
28290        1
28291        2
28292        3
28293        "});
28294
28295    // Scenario 3: Select "2", then fold first buffer before insertion
28296    cx.update_multibuffer(|mb, cx| {
28297        for buffer_id in buffer_ids.iter() {
28298            let buffer = mb.buffer(*buffer_id).unwrap();
28299            buffer.update(cx, |buffer, cx| {
28300                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28301            });
28302        }
28303    });
28304
28305    // Select "2" and select all matches
28306    cx.update_editor(|editor, window, cx| {
28307        editor.change_selections(None.into(), window, cx, |s| {
28308            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28309        });
28310        editor
28311            .select_all_matches(&SelectAllMatches, window, cx)
28312            .unwrap();
28313    });
28314
28315    // Fold first buffer - should remove selections from folded buffer
28316    cx.update_editor(|editor, _, cx| {
28317        editor.fold_buffer(buffer_ids[0], cx);
28318    });
28319    cx.assert_excerpts_with_selections(indoc! {"
28320        [EXCERPT]
28321        [FOLDED]
28322        [EXCERPT]
28323        1
2832428325        3
28326        "});
28327
28328    // Insert text - should only affect second buffer
28329    cx.update_editor(|editor, window, cx| {
28330        editor.handle_input("Z", window, cx);
28331    });
28332    cx.update_editor(|editor, _, cx| {
28333        editor.unfold_buffer(buffer_ids[0], cx);
28334    });
28335    cx.assert_excerpts_with_selections(indoc! {"
28336        [EXCERPT]
28337        1
28338        2
28339        3
28340        [EXCERPT]
28341        1
2834228343        3
28344        "});
28345
28346    // Edge case scenario: fold all buffers, then try to insert
28347    cx.update_editor(|editor, _, cx| {
28348        editor.fold_buffer(buffer_ids[0], cx);
28349        editor.fold_buffer(buffer_ids[1], cx);
28350    });
28351    cx.assert_excerpts_with_selections(indoc! {"
28352        [EXCERPT]
28353        ˇ[FOLDED]
28354        [EXCERPT]
28355        [FOLDED]
28356        "});
28357
28358    // Insert should work via default selection
28359    cx.update_editor(|editor, window, cx| {
28360        editor.handle_input("W", window, cx);
28361    });
28362    cx.update_editor(|editor, _, cx| {
28363        editor.unfold_buffer(buffer_ids[0], cx);
28364        editor.unfold_buffer(buffer_ids[1], cx);
28365    });
28366    cx.assert_excerpts_with_selections(indoc! {"
28367        [EXCERPT]
28368        Wˇ1
28369        2
28370        3
28371        [EXCERPT]
28372        1
28373        Z
28374        3
28375        "});
28376}
28377
28378#[gpui::test]
28379async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
28380    init_test(cx, |_| {});
28381    let mut leader_cx = EditorTestContext::new(cx).await;
28382
28383    let diff_base = indoc!(
28384        r#"
28385        one
28386        two
28387        three
28388        four
28389        five
28390        six
28391        "#
28392    );
28393
28394    let initial_state = indoc!(
28395        r#"
28396        ˇone
28397        two
28398        THREE
28399        four
28400        five
28401        six
28402        "#
28403    );
28404
28405    leader_cx.set_state(initial_state);
28406
28407    leader_cx.set_head_text(&diff_base);
28408    leader_cx.run_until_parked();
28409
28410    let follower = leader_cx.update_multibuffer(|leader, cx| {
28411        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28412        leader.set_all_diff_hunks_expanded(cx);
28413        leader.get_or_create_follower(cx)
28414    });
28415    follower.update(cx, |follower, cx| {
28416        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28417        follower.set_all_diff_hunks_expanded(cx);
28418    });
28419
28420    let follower_editor =
28421        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
28422    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
28423
28424    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
28425    cx.run_until_parked();
28426
28427    leader_cx.assert_editor_state(initial_state);
28428    follower_cx.assert_editor_state(indoc! {
28429        r#"
28430        ˇone
28431        two
28432        three
28433        four
28434        five
28435        six
28436        "#
28437    });
28438
28439    follower_cx.editor(|editor, _window, cx| {
28440        assert!(editor.read_only(cx));
28441    });
28442
28443    leader_cx.update_editor(|editor, _window, cx| {
28444        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
28445    });
28446    cx.run_until_parked();
28447
28448    leader_cx.assert_editor_state(indoc! {
28449        r#"
28450        ˇone
28451        two
28452        THREE
28453        four
28454        FIVE
28455        six
28456        "#
28457    });
28458
28459    follower_cx.assert_editor_state(indoc! {
28460        r#"
28461        ˇone
28462        two
28463        three
28464        four
28465        five
28466        six
28467        "#
28468    });
28469
28470    leader_cx.update_editor(|editor, _window, cx| {
28471        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
28472    });
28473    cx.run_until_parked();
28474
28475    leader_cx.assert_editor_state(indoc! {
28476        r#"
28477        ˇone
28478        two
28479        THREE
28480        four
28481        FIVE
28482        six
28483        SEVEN"#
28484    });
28485
28486    follower_cx.assert_editor_state(indoc! {
28487        r#"
28488        ˇone
28489        two
28490        three
28491        four
28492        five
28493        six
28494        "#
28495    });
28496
28497    leader_cx.update_editor(|editor, window, cx| {
28498        editor.move_down(&MoveDown, window, cx);
28499        editor.refresh_selected_text_highlights(true, window, cx);
28500    });
28501    leader_cx.run_until_parked();
28502}
28503
28504#[gpui::test]
28505async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
28506    init_test(cx, |_| {});
28507    let base_text = "base\n";
28508    let buffer_text = "buffer\n";
28509
28510    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
28511    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
28512
28513    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
28514    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
28515    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
28516    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
28517
28518    let leader = cx.new(|cx| {
28519        let mut leader = MultiBuffer::new(Capability::ReadWrite);
28520        leader.set_all_diff_hunks_expanded(cx);
28521        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28522        leader
28523    });
28524    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
28525    follower.update(cx, |follower, _| {
28526        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28527    });
28528
28529    leader.update(cx, |leader, cx| {
28530        leader.insert_excerpts_after(
28531            ExcerptId::min(),
28532            extra_buffer_2.clone(),
28533            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28534            cx,
28535        );
28536        leader.add_diff(extra_diff_2.clone(), cx);
28537
28538        leader.insert_excerpts_after(
28539            ExcerptId::min(),
28540            extra_buffer_1.clone(),
28541            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28542            cx,
28543        );
28544        leader.add_diff(extra_diff_1.clone(), cx);
28545
28546        leader.insert_excerpts_after(
28547            ExcerptId::min(),
28548            buffer1.clone(),
28549            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28550            cx,
28551        );
28552        leader.add_diff(diff1.clone(), cx);
28553    });
28554
28555    cx.run_until_parked();
28556    let mut cx = cx.add_empty_window();
28557
28558    let leader_editor = cx
28559        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
28560    let follower_editor = cx.new_window_entity(|window, cx| {
28561        Editor::for_multibuffer(follower.clone(), None, window, cx)
28562    });
28563
28564    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
28565    leader_cx.assert_editor_state(indoc! {"
28566       ˇbuffer
28567
28568       dummy text 1
28569
28570       dummy text 2
28571    "});
28572    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
28573    follower_cx.assert_editor_state(indoc! {"
28574        ˇbase
28575
28576
28577    "});
28578}
28579
28580#[gpui::test]
28581async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
28582    init_test(cx, |_| {});
28583
28584    let (editor, cx) = cx.add_window_view(|window, cx| {
28585        let multi_buffer = MultiBuffer::build_multi(
28586            [
28587                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28588                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
28589            ],
28590            cx,
28591        );
28592        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28593    });
28594
28595    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28596
28597    cx.assert_excerpts_with_selections(indoc! {"
28598        [EXCERPT]
28599        ˇ1
28600        2
28601        3
28602        [EXCERPT]
28603        1
28604        2
28605        3
28606        4
28607        5
28608        6
28609        7
28610        8
28611        9
28612        "});
28613
28614    cx.update_editor(|editor, window, cx| {
28615        editor.change_selections(None.into(), window, cx, |s| {
28616            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
28617        });
28618    });
28619
28620    cx.assert_excerpts_with_selections(indoc! {"
28621        [EXCERPT]
28622        1
28623        2
28624        3
28625        [EXCERPT]
28626        1
28627        2
28628        3
28629        4
28630        5
28631        6
28632        ˇ7
28633        8
28634        9
28635        "});
28636
28637    cx.update_editor(|editor, _window, cx| {
28638        editor.set_vertical_scroll_margin(0, cx);
28639    });
28640
28641    cx.update_editor(|editor, window, cx| {
28642        assert_eq!(editor.vertical_scroll_margin(), 0);
28643        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28644        assert_eq!(
28645            editor.snapshot(window, cx).scroll_position(),
28646            gpui::Point::new(0., 12.0)
28647        );
28648    });
28649
28650    cx.update_editor(|editor, _window, cx| {
28651        editor.set_vertical_scroll_margin(3, cx);
28652    });
28653
28654    cx.update_editor(|editor, window, cx| {
28655        assert_eq!(editor.vertical_scroll_margin(), 3);
28656        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28657        assert_eq!(
28658            editor.snapshot(window, cx).scroll_position(),
28659            gpui::Point::new(0., 9.0)
28660        );
28661    });
28662}