editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionDelegate,
    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.update_editor(|editor, window, cx| {
 2222        editor
 2223            .style(cx)
 2224            .text
 2225            .line_height_in_pixels(window.rem_size())
 2226    });
 2227    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2228
 2229    cx.set_state(
 2230        &r#"ˇone
 2231        two
 2232
 2233        three
 2234        fourˇ
 2235        five
 2236
 2237        six"#
 2238            .unindent(),
 2239    );
 2240
 2241    cx.update_editor(|editor, window, cx| {
 2242        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2243    });
 2244    cx.assert_editor_state(
 2245        &r#"one
 2246        two
 2247        ˇ
 2248        three
 2249        four
 2250        five
 2251        ˇ
 2252        six"#
 2253            .unindent(),
 2254    );
 2255
 2256    cx.update_editor(|editor, window, cx| {
 2257        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2258    });
 2259    cx.assert_editor_state(
 2260        &r#"one
 2261        two
 2262
 2263        three
 2264        four
 2265        five
 2266        ˇ
 2267        sixˇ"#
 2268            .unindent(),
 2269    );
 2270
 2271    cx.update_editor(|editor, window, cx| {
 2272        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2273    });
 2274    cx.assert_editor_state(
 2275        &r#"one
 2276        two
 2277
 2278        three
 2279        four
 2280        five
 2281
 2282        sixˇ"#
 2283            .unindent(),
 2284    );
 2285
 2286    cx.update_editor(|editor, window, cx| {
 2287        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2288    });
 2289    cx.assert_editor_state(
 2290        &r#"one
 2291        two
 2292
 2293        three
 2294        four
 2295        five
 2296        ˇ
 2297        six"#
 2298            .unindent(),
 2299    );
 2300
 2301    cx.update_editor(|editor, window, cx| {
 2302        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2303    });
 2304    cx.assert_editor_state(
 2305        &r#"one
 2306        two
 2307        ˇ
 2308        three
 2309        four
 2310        five
 2311
 2312        six"#
 2313            .unindent(),
 2314    );
 2315
 2316    cx.update_editor(|editor, window, cx| {
 2317        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2318    });
 2319    cx.assert_editor_state(
 2320        &r#"ˇone
 2321        two
 2322
 2323        three
 2324        four
 2325        five
 2326
 2327        six"#
 2328            .unindent(),
 2329    );
 2330}
 2331
 2332#[gpui::test]
 2333async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2334    init_test(cx, |_| {});
 2335    let mut cx = EditorTestContext::new(cx).await;
 2336    let line_height = cx.update_editor(|editor, window, cx| {
 2337        editor
 2338            .style(cx)
 2339            .text
 2340            .line_height_in_pixels(window.rem_size())
 2341    });
 2342    let window = cx.window;
 2343    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2344
 2345    cx.set_state(
 2346        r#"ˇone
 2347        two
 2348        three
 2349        four
 2350        five
 2351        six
 2352        seven
 2353        eight
 2354        nine
 2355        ten
 2356        "#,
 2357    );
 2358
 2359    cx.update_editor(|editor, window, cx| {
 2360        assert_eq!(
 2361            editor.snapshot(window, cx).scroll_position(),
 2362            gpui::Point::new(0., 0.)
 2363        );
 2364        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2365        assert_eq!(
 2366            editor.snapshot(window, cx).scroll_position(),
 2367            gpui::Point::new(0., 3.)
 2368        );
 2369        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2370        assert_eq!(
 2371            editor.snapshot(window, cx).scroll_position(),
 2372            gpui::Point::new(0., 6.)
 2373        );
 2374        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2375        assert_eq!(
 2376            editor.snapshot(window, cx).scroll_position(),
 2377            gpui::Point::new(0., 3.)
 2378        );
 2379
 2380        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2381        assert_eq!(
 2382            editor.snapshot(window, cx).scroll_position(),
 2383            gpui::Point::new(0., 1.)
 2384        );
 2385        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2386        assert_eq!(
 2387            editor.snapshot(window, cx).scroll_position(),
 2388            gpui::Point::new(0., 3.)
 2389        );
 2390    });
 2391}
 2392
 2393#[gpui::test]
 2394async fn test_autoscroll(cx: &mut TestAppContext) {
 2395    init_test(cx, |_| {});
 2396    let mut cx = EditorTestContext::new(cx).await;
 2397
 2398    let line_height = cx.update_editor(|editor, window, cx| {
 2399        editor.set_vertical_scroll_margin(2, cx);
 2400        editor
 2401            .style(cx)
 2402            .text
 2403            .line_height_in_pixels(window.rem_size())
 2404    });
 2405    let window = cx.window;
 2406    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2407
 2408    cx.set_state(
 2409        r#"ˇone
 2410            two
 2411            three
 2412            four
 2413            five
 2414            six
 2415            seven
 2416            eight
 2417            nine
 2418            ten
 2419        "#,
 2420    );
 2421    cx.update_editor(|editor, window, cx| {
 2422        assert_eq!(
 2423            editor.snapshot(window, cx).scroll_position(),
 2424            gpui::Point::new(0., 0.0)
 2425        );
 2426    });
 2427
 2428    // Add a cursor below the visible area. Since both cursors cannot fit
 2429    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2430    // allows the vertical scroll margin below that cursor.
 2431    cx.update_editor(|editor, window, cx| {
 2432        editor.change_selections(Default::default(), window, cx, |selections| {
 2433            selections.select_ranges([
 2434                Point::new(0, 0)..Point::new(0, 0),
 2435                Point::new(6, 0)..Point::new(6, 0),
 2436            ]);
 2437        })
 2438    });
 2439    cx.update_editor(|editor, window, cx| {
 2440        assert_eq!(
 2441            editor.snapshot(window, cx).scroll_position(),
 2442            gpui::Point::new(0., 3.0)
 2443        );
 2444    });
 2445
 2446    // Move down. The editor cursor scrolls down to track the newest cursor.
 2447    cx.update_editor(|editor, window, cx| {
 2448        editor.move_down(&Default::default(), window, cx);
 2449    });
 2450    cx.update_editor(|editor, window, cx| {
 2451        assert_eq!(
 2452            editor.snapshot(window, cx).scroll_position(),
 2453            gpui::Point::new(0., 4.0)
 2454        );
 2455    });
 2456
 2457    // Add a cursor above the visible area. Since both cursors fit on screen,
 2458    // the editor scrolls to show both.
 2459    cx.update_editor(|editor, window, cx| {
 2460        editor.change_selections(Default::default(), window, cx, |selections| {
 2461            selections.select_ranges([
 2462                Point::new(1, 0)..Point::new(1, 0),
 2463                Point::new(6, 0)..Point::new(6, 0),
 2464            ]);
 2465        })
 2466    });
 2467    cx.update_editor(|editor, window, cx| {
 2468        assert_eq!(
 2469            editor.snapshot(window, cx).scroll_position(),
 2470            gpui::Point::new(0., 1.0)
 2471        );
 2472    });
 2473}
 2474
 2475#[gpui::test]
 2476async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2477    init_test(cx, |_| {});
 2478    let mut cx = EditorTestContext::new(cx).await;
 2479
 2480    let line_height = cx.update_editor(|editor, window, cx| {
 2481        editor
 2482            .style(cx)
 2483            .text
 2484            .line_height_in_pixels(window.rem_size())
 2485    });
 2486    let window = cx.window;
 2487    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2488    cx.set_state(
 2489        &r#"
 2490        ˇone
 2491        two
 2492        threeˇ
 2493        four
 2494        five
 2495        six
 2496        seven
 2497        eight
 2498        nine
 2499        ten
 2500        "#
 2501        .unindent(),
 2502    );
 2503
 2504    cx.update_editor(|editor, window, cx| {
 2505        editor.move_page_down(&MovePageDown::default(), window, cx)
 2506    });
 2507    cx.assert_editor_state(
 2508        &r#"
 2509        one
 2510        two
 2511        three
 2512        ˇfour
 2513        five
 2514        sixˇ
 2515        seven
 2516        eight
 2517        nine
 2518        ten
 2519        "#
 2520        .unindent(),
 2521    );
 2522
 2523    cx.update_editor(|editor, window, cx| {
 2524        editor.move_page_down(&MovePageDown::default(), window, cx)
 2525    });
 2526    cx.assert_editor_state(
 2527        &r#"
 2528        one
 2529        two
 2530        three
 2531        four
 2532        five
 2533        six
 2534        ˇseven
 2535        eight
 2536        nineˇ
 2537        ten
 2538        "#
 2539        .unindent(),
 2540    );
 2541
 2542    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2543    cx.assert_editor_state(
 2544        &r#"
 2545        one
 2546        two
 2547        three
 2548        ˇfour
 2549        five
 2550        sixˇ
 2551        seven
 2552        eight
 2553        nine
 2554        ten
 2555        "#
 2556        .unindent(),
 2557    );
 2558
 2559    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2560    cx.assert_editor_state(
 2561        &r#"
 2562        ˇone
 2563        two
 2564        threeˇ
 2565        four
 2566        five
 2567        six
 2568        seven
 2569        eight
 2570        nine
 2571        ten
 2572        "#
 2573        .unindent(),
 2574    );
 2575
 2576    // Test select collapsing
 2577    cx.update_editor(|editor, window, cx| {
 2578        editor.move_page_down(&MovePageDown::default(), window, cx);
 2579        editor.move_page_down(&MovePageDown::default(), window, cx);
 2580        editor.move_page_down(&MovePageDown::default(), window, cx);
 2581    });
 2582    cx.assert_editor_state(
 2583        &r#"
 2584        one
 2585        two
 2586        three
 2587        four
 2588        five
 2589        six
 2590        seven
 2591        eight
 2592        nine
 2593        ˇten
 2594        ˇ"#
 2595        .unindent(),
 2596    );
 2597}
 2598
 2599#[gpui::test]
 2600async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2601    init_test(cx, |_| {});
 2602    let mut cx = EditorTestContext::new(cx).await;
 2603    cx.set_state("one «two threeˇ» four");
 2604    cx.update_editor(|editor, window, cx| {
 2605        editor.delete_to_beginning_of_line(
 2606            &DeleteToBeginningOfLine {
 2607                stop_at_indent: false,
 2608            },
 2609            window,
 2610            cx,
 2611        );
 2612        assert_eq!(editor.text(cx), " four");
 2613    });
 2614}
 2615
 2616#[gpui::test]
 2617async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2618    init_test(cx, |_| {});
 2619
 2620    let mut cx = EditorTestContext::new(cx).await;
 2621
 2622    // For an empty selection, the preceding word fragment is deleted.
 2623    // For non-empty selections, only selected characters are deleted.
 2624    cx.set_state("onˇe two t«hreˇ»e four");
 2625    cx.update_editor(|editor, window, cx| {
 2626        editor.delete_to_previous_word_start(
 2627            &DeleteToPreviousWordStart {
 2628                ignore_newlines: false,
 2629                ignore_brackets: false,
 2630            },
 2631            window,
 2632            cx,
 2633        );
 2634    });
 2635    cx.assert_editor_state("ˇe two tˇe four");
 2636
 2637    cx.set_state("e tˇwo te «fˇ»our");
 2638    cx.update_editor(|editor, window, cx| {
 2639        editor.delete_to_next_word_end(
 2640            &DeleteToNextWordEnd {
 2641                ignore_newlines: false,
 2642                ignore_brackets: false,
 2643            },
 2644            window,
 2645            cx,
 2646        );
 2647    });
 2648    cx.assert_editor_state("e tˇ te ˇour");
 2649}
 2650
 2651#[gpui::test]
 2652async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2653    init_test(cx, |_| {});
 2654
 2655    let mut cx = EditorTestContext::new(cx).await;
 2656
 2657    cx.set_state("here is some text    ˇwith a space");
 2658    cx.update_editor(|editor, window, cx| {
 2659        editor.delete_to_previous_word_start(
 2660            &DeleteToPreviousWordStart {
 2661                ignore_newlines: false,
 2662                ignore_brackets: true,
 2663            },
 2664            window,
 2665            cx,
 2666        );
 2667    });
 2668    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2669    cx.assert_editor_state("here is some textˇwith a space");
 2670
 2671    cx.set_state("here is some text    ˇwith a space");
 2672    cx.update_editor(|editor, window, cx| {
 2673        editor.delete_to_previous_word_start(
 2674            &DeleteToPreviousWordStart {
 2675                ignore_newlines: false,
 2676                ignore_brackets: false,
 2677            },
 2678            window,
 2679            cx,
 2680        );
 2681    });
 2682    cx.assert_editor_state("here is some textˇwith a space");
 2683
 2684    cx.set_state("here is some textˇ    with a space");
 2685    cx.update_editor(|editor, window, cx| {
 2686        editor.delete_to_next_word_end(
 2687            &DeleteToNextWordEnd {
 2688                ignore_newlines: false,
 2689                ignore_brackets: true,
 2690            },
 2691            window,
 2692            cx,
 2693        );
 2694    });
 2695    // Same happens in the other direction.
 2696    cx.assert_editor_state("here is some textˇwith a space");
 2697
 2698    cx.set_state("here is some textˇ    with a space");
 2699    cx.update_editor(|editor, window, cx| {
 2700        editor.delete_to_next_word_end(
 2701            &DeleteToNextWordEnd {
 2702                ignore_newlines: false,
 2703                ignore_brackets: false,
 2704            },
 2705            window,
 2706            cx,
 2707        );
 2708    });
 2709    cx.assert_editor_state("here is some textˇwith a space");
 2710
 2711    cx.set_state("here is some textˇ    with a space");
 2712    cx.update_editor(|editor, window, cx| {
 2713        editor.delete_to_next_word_end(
 2714            &DeleteToNextWordEnd {
 2715                ignore_newlines: true,
 2716                ignore_brackets: false,
 2717            },
 2718            window,
 2719            cx,
 2720        );
 2721    });
 2722    cx.assert_editor_state("here is some textˇwith a space");
 2723    cx.update_editor(|editor, window, cx| {
 2724        editor.delete_to_previous_word_start(
 2725            &DeleteToPreviousWordStart {
 2726                ignore_newlines: true,
 2727                ignore_brackets: false,
 2728            },
 2729            window,
 2730            cx,
 2731        );
 2732    });
 2733    cx.assert_editor_state("here is some ˇwith a space");
 2734    cx.update_editor(|editor, window, cx| {
 2735        editor.delete_to_previous_word_start(
 2736            &DeleteToPreviousWordStart {
 2737                ignore_newlines: true,
 2738                ignore_brackets: false,
 2739            },
 2740            window,
 2741            cx,
 2742        );
 2743    });
 2744    // Single whitespaces are removed with the word behind them.
 2745    cx.assert_editor_state("here is ˇwith a space");
 2746    cx.update_editor(|editor, window, cx| {
 2747        editor.delete_to_previous_word_start(
 2748            &DeleteToPreviousWordStart {
 2749                ignore_newlines: true,
 2750                ignore_brackets: false,
 2751            },
 2752            window,
 2753            cx,
 2754        );
 2755    });
 2756    cx.assert_editor_state("here ˇwith a space");
 2757    cx.update_editor(|editor, window, cx| {
 2758        editor.delete_to_previous_word_start(
 2759            &DeleteToPreviousWordStart {
 2760                ignore_newlines: true,
 2761                ignore_brackets: false,
 2762            },
 2763            window,
 2764            cx,
 2765        );
 2766    });
 2767    cx.assert_editor_state("ˇwith a space");
 2768    cx.update_editor(|editor, window, cx| {
 2769        editor.delete_to_previous_word_start(
 2770            &DeleteToPreviousWordStart {
 2771                ignore_newlines: true,
 2772                ignore_brackets: false,
 2773            },
 2774            window,
 2775            cx,
 2776        );
 2777    });
 2778    cx.assert_editor_state("ˇwith a space");
 2779    cx.update_editor(|editor, window, cx| {
 2780        editor.delete_to_next_word_end(
 2781            &DeleteToNextWordEnd {
 2782                ignore_newlines: true,
 2783                ignore_brackets: false,
 2784            },
 2785            window,
 2786            cx,
 2787        );
 2788    });
 2789    // Same happens in the other direction.
 2790    cx.assert_editor_state("ˇ a space");
 2791    cx.update_editor(|editor, window, cx| {
 2792        editor.delete_to_next_word_end(
 2793            &DeleteToNextWordEnd {
 2794                ignore_newlines: true,
 2795                ignore_brackets: false,
 2796            },
 2797            window,
 2798            cx,
 2799        );
 2800    });
 2801    cx.assert_editor_state("ˇ space");
 2802    cx.update_editor(|editor, window, cx| {
 2803        editor.delete_to_next_word_end(
 2804            &DeleteToNextWordEnd {
 2805                ignore_newlines: true,
 2806                ignore_brackets: false,
 2807            },
 2808            window,
 2809            cx,
 2810        );
 2811    });
 2812    cx.assert_editor_state("ˇ");
 2813    cx.update_editor(|editor, window, cx| {
 2814        editor.delete_to_next_word_end(
 2815            &DeleteToNextWordEnd {
 2816                ignore_newlines: true,
 2817                ignore_brackets: false,
 2818            },
 2819            window,
 2820            cx,
 2821        );
 2822    });
 2823    cx.assert_editor_state("ˇ");
 2824    cx.update_editor(|editor, window, cx| {
 2825        editor.delete_to_previous_word_start(
 2826            &DeleteToPreviousWordStart {
 2827                ignore_newlines: true,
 2828                ignore_brackets: false,
 2829            },
 2830            window,
 2831            cx,
 2832        );
 2833    });
 2834    cx.assert_editor_state("ˇ");
 2835}
 2836
 2837#[gpui::test]
 2838async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2839    init_test(cx, |_| {});
 2840
 2841    let language = Arc::new(
 2842        Language::new(
 2843            LanguageConfig {
 2844                brackets: BracketPairConfig {
 2845                    pairs: vec![
 2846                        BracketPair {
 2847                            start: "\"".to_string(),
 2848                            end: "\"".to_string(),
 2849                            close: true,
 2850                            surround: true,
 2851                            newline: false,
 2852                        },
 2853                        BracketPair {
 2854                            start: "(".to_string(),
 2855                            end: ")".to_string(),
 2856                            close: true,
 2857                            surround: true,
 2858                            newline: true,
 2859                        },
 2860                    ],
 2861                    ..BracketPairConfig::default()
 2862                },
 2863                ..LanguageConfig::default()
 2864            },
 2865            Some(tree_sitter_rust::LANGUAGE.into()),
 2866        )
 2867        .with_brackets_query(
 2868            r#"
 2869                ("(" @open ")" @close)
 2870                ("\"" @open "\"" @close)
 2871            "#,
 2872        )
 2873        .unwrap(),
 2874    );
 2875
 2876    let mut cx = EditorTestContext::new(cx).await;
 2877    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2878
 2879    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2880    cx.update_editor(|editor, window, cx| {
 2881        editor.delete_to_previous_word_start(
 2882            &DeleteToPreviousWordStart {
 2883                ignore_newlines: true,
 2884                ignore_brackets: false,
 2885            },
 2886            window,
 2887            cx,
 2888        );
 2889    });
 2890    // Deletion stops before brackets if asked to not ignore them.
 2891    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2892    cx.update_editor(|editor, window, cx| {
 2893        editor.delete_to_previous_word_start(
 2894            &DeleteToPreviousWordStart {
 2895                ignore_newlines: true,
 2896                ignore_brackets: false,
 2897            },
 2898            window,
 2899            cx,
 2900        );
 2901    });
 2902    // Deletion has to remove a single bracket and then stop again.
 2903    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2904
 2905    cx.update_editor(|editor, window, cx| {
 2906        editor.delete_to_previous_word_start(
 2907            &DeleteToPreviousWordStart {
 2908                ignore_newlines: true,
 2909                ignore_brackets: false,
 2910            },
 2911            window,
 2912            cx,
 2913        );
 2914    });
 2915    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2916
 2917    cx.update_editor(|editor, window, cx| {
 2918        editor.delete_to_previous_word_start(
 2919            &DeleteToPreviousWordStart {
 2920                ignore_newlines: true,
 2921                ignore_brackets: false,
 2922            },
 2923            window,
 2924            cx,
 2925        );
 2926    });
 2927    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2928
 2929    cx.update_editor(|editor, window, cx| {
 2930        editor.delete_to_previous_word_start(
 2931            &DeleteToPreviousWordStart {
 2932                ignore_newlines: true,
 2933                ignore_brackets: false,
 2934            },
 2935            window,
 2936            cx,
 2937        );
 2938    });
 2939    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2940
 2941    cx.update_editor(|editor, window, cx| {
 2942        editor.delete_to_next_word_end(
 2943            &DeleteToNextWordEnd {
 2944                ignore_newlines: true,
 2945                ignore_brackets: false,
 2946            },
 2947            window,
 2948            cx,
 2949        );
 2950    });
 2951    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2952    cx.assert_editor_state(r#"ˇ");"#);
 2953
 2954    cx.update_editor(|editor, window, cx| {
 2955        editor.delete_to_next_word_end(
 2956            &DeleteToNextWordEnd {
 2957                ignore_newlines: true,
 2958                ignore_brackets: false,
 2959            },
 2960            window,
 2961            cx,
 2962        );
 2963    });
 2964    cx.assert_editor_state(r#"ˇ"#);
 2965
 2966    cx.update_editor(|editor, window, cx| {
 2967        editor.delete_to_next_word_end(
 2968            &DeleteToNextWordEnd {
 2969                ignore_newlines: true,
 2970                ignore_brackets: false,
 2971            },
 2972            window,
 2973            cx,
 2974        );
 2975    });
 2976    cx.assert_editor_state(r#"ˇ"#);
 2977
 2978    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2979    cx.update_editor(|editor, window, cx| {
 2980        editor.delete_to_previous_word_start(
 2981            &DeleteToPreviousWordStart {
 2982                ignore_newlines: true,
 2983                ignore_brackets: true,
 2984            },
 2985            window,
 2986            cx,
 2987        );
 2988    });
 2989    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2990}
 2991
 2992#[gpui::test]
 2993fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2994    init_test(cx, |_| {});
 2995
 2996    let editor = cx.add_window(|window, cx| {
 2997        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 2998        build_editor(buffer, window, cx)
 2999    });
 3000    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3001        ignore_newlines: false,
 3002        ignore_brackets: false,
 3003    };
 3004    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3005        ignore_newlines: true,
 3006        ignore_brackets: false,
 3007    };
 3008
 3009    _ = editor.update(cx, |editor, window, cx| {
 3010        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3011            s.select_display_ranges([
 3012                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3013            ])
 3014        });
 3015        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3016        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3017        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3018        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 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\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");
 3023        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3024        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3025        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3026        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3027    });
 3028}
 3029
 3030#[gpui::test]
 3031fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3032    init_test(cx, |_| {});
 3033
 3034    let editor = cx.add_window(|window, cx| {
 3035        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3036        build_editor(buffer, window, cx)
 3037    });
 3038    let del_to_next_word_end = DeleteToNextWordEnd {
 3039        ignore_newlines: false,
 3040        ignore_brackets: false,
 3041    };
 3042    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3043        ignore_newlines: true,
 3044        ignore_brackets: false,
 3045    };
 3046
 3047    _ = editor.update(cx, |editor, window, cx| {
 3048        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3049            s.select_display_ranges([
 3050                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3051            ])
 3052        });
 3053        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3054        assert_eq!(
 3055            editor.buffer.read(cx).read(cx).text(),
 3056            "one\n   two\nthree\n   four"
 3057        );
 3058        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3059        assert_eq!(
 3060            editor.buffer.read(cx).read(cx).text(),
 3061            "\n   two\nthree\n   four"
 3062        );
 3063        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3064        assert_eq!(
 3065            editor.buffer.read(cx).read(cx).text(),
 3066            "two\nthree\n   four"
 3067        );
 3068        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3069        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3070        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3071        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3072        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3073        assert_eq!(editor.buffer.read(cx).read(cx).text(), "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(), "");
 3076    });
 3077}
 3078
 3079#[gpui::test]
 3080fn test_newline(cx: &mut TestAppContext) {
 3081    init_test(cx, |_| {});
 3082
 3083    let editor = cx.add_window(|window, cx| {
 3084        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3085        build_editor(buffer, window, cx)
 3086    });
 3087
 3088    _ = editor.update(cx, |editor, window, cx| {
 3089        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3090            s.select_display_ranges([
 3091                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3092                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3093                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3094            ])
 3095        });
 3096
 3097        editor.newline(&Newline, window, cx);
 3098        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3099    });
 3100}
 3101
 3102#[gpui::test]
 3103async fn test_newline_yaml(cx: &mut TestAppContext) {
 3104    init_test(cx, |_| {});
 3105
 3106    let mut cx = EditorTestContext::new(cx).await;
 3107    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3108    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3109
 3110    // Object (between 2 fields)
 3111    cx.set_state(indoc! {"
 3112    test:ˇ
 3113    hello: bye"});
 3114    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3115    cx.assert_editor_state(indoc! {"
 3116    test:
 3117        ˇ
 3118    hello: bye"});
 3119
 3120    // Object (first and single line)
 3121    cx.set_state(indoc! {"
 3122    test:ˇ"});
 3123    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3124    cx.assert_editor_state(indoc! {"
 3125    test:
 3126        ˇ"});
 3127
 3128    // Array with objects (after first element)
 3129    cx.set_state(indoc! {"
 3130    test:
 3131        - foo: barˇ"});
 3132    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3133    cx.assert_editor_state(indoc! {"
 3134    test:
 3135        - foo: bar
 3136        ˇ"});
 3137
 3138    // Array with objects and comment
 3139    cx.set_state(indoc! {"
 3140    test:
 3141        - foo: bar
 3142        - bar: # testˇ"});
 3143    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3144    cx.assert_editor_state(indoc! {"
 3145    test:
 3146        - foo: bar
 3147        - bar: # test
 3148            ˇ"});
 3149
 3150    // Array with objects (after second element)
 3151    cx.set_state(indoc! {"
 3152    test:
 3153        - foo: bar
 3154        - bar: fooˇ"});
 3155    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3156    cx.assert_editor_state(indoc! {"
 3157    test:
 3158        - foo: bar
 3159        - bar: foo
 3160        ˇ"});
 3161
 3162    // Array with strings (after first element)
 3163    cx.set_state(indoc! {"
 3164    test:
 3165        - fooˇ"});
 3166    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3167    cx.assert_editor_state(indoc! {"
 3168    test:
 3169        - foo
 3170        ˇ"});
 3171}
 3172
 3173#[gpui::test]
 3174fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3175    init_test(cx, |_| {});
 3176
 3177    let editor = cx.add_window(|window, cx| {
 3178        let buffer = MultiBuffer::build_simple(
 3179            "
 3180                a
 3181                b(
 3182                    X
 3183                )
 3184                c(
 3185                    X
 3186                )
 3187            "
 3188            .unindent()
 3189            .as_str(),
 3190            cx,
 3191        );
 3192        let mut editor = build_editor(buffer, window, cx);
 3193        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3194            s.select_ranges([
 3195                Point::new(2, 4)..Point::new(2, 5),
 3196                Point::new(5, 4)..Point::new(5, 5),
 3197            ])
 3198        });
 3199        editor
 3200    });
 3201
 3202    _ = editor.update(cx, |editor, window, cx| {
 3203        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3204        editor.buffer.update(cx, |buffer, cx| {
 3205            buffer.edit(
 3206                [
 3207                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3208                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3209                ],
 3210                None,
 3211                cx,
 3212            );
 3213            assert_eq!(
 3214                buffer.read(cx).text(),
 3215                "
 3216                    a
 3217                    b()
 3218                    c()
 3219                "
 3220                .unindent()
 3221            );
 3222        });
 3223        assert_eq!(
 3224            editor.selections.ranges(&editor.display_snapshot(cx)),
 3225            &[
 3226                Point::new(1, 2)..Point::new(1, 2),
 3227                Point::new(2, 2)..Point::new(2, 2),
 3228            ],
 3229        );
 3230
 3231        editor.newline(&Newline, window, cx);
 3232        assert_eq!(
 3233            editor.text(cx),
 3234            "
 3235                a
 3236                b(
 3237                )
 3238                c(
 3239                )
 3240            "
 3241            .unindent()
 3242        );
 3243
 3244        // The selections are moved after the inserted newlines
 3245        assert_eq!(
 3246            editor.selections.ranges(&editor.display_snapshot(cx)),
 3247            &[
 3248                Point::new(2, 0)..Point::new(2, 0),
 3249                Point::new(4, 0)..Point::new(4, 0),
 3250            ],
 3251        );
 3252    });
 3253}
 3254
 3255#[gpui::test]
 3256async fn test_newline_above(cx: &mut TestAppContext) {
 3257    init_test(cx, |settings| {
 3258        settings.defaults.tab_size = NonZeroU32::new(4)
 3259    });
 3260
 3261    let language = Arc::new(
 3262        Language::new(
 3263            LanguageConfig::default(),
 3264            Some(tree_sitter_rust::LANGUAGE.into()),
 3265        )
 3266        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3267        .unwrap(),
 3268    );
 3269
 3270    let mut cx = EditorTestContext::new(cx).await;
 3271    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3272    cx.set_state(indoc! {"
 3273        const a: ˇA = (
 3274 3275                «const_functionˇ»(ˇ),
 3276                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3277 3278        ˇ);ˇ
 3279    "});
 3280
 3281    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3282    cx.assert_editor_state(indoc! {"
 3283        ˇ
 3284        const a: A = (
 3285            ˇ
 3286            (
 3287                ˇ
 3288                ˇ
 3289                const_function(),
 3290                ˇ
 3291                ˇ
 3292                ˇ
 3293                ˇ
 3294                something_else,
 3295                ˇ
 3296            )
 3297            ˇ
 3298            ˇ
 3299        );
 3300    "});
 3301}
 3302
 3303#[gpui::test]
 3304async fn test_newline_below(cx: &mut TestAppContext) {
 3305    init_test(cx, |settings| {
 3306        settings.defaults.tab_size = NonZeroU32::new(4)
 3307    });
 3308
 3309    let language = Arc::new(
 3310        Language::new(
 3311            LanguageConfig::default(),
 3312            Some(tree_sitter_rust::LANGUAGE.into()),
 3313        )
 3314        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3315        .unwrap(),
 3316    );
 3317
 3318    let mut cx = EditorTestContext::new(cx).await;
 3319    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3320    cx.set_state(indoc! {"
 3321        const a: ˇA = (
 3322 3323                «const_functionˇ»(ˇ),
 3324                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3325 3326        ˇ);ˇ
 3327    "});
 3328
 3329    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3330    cx.assert_editor_state(indoc! {"
 3331        const a: A = (
 3332            ˇ
 3333            (
 3334                ˇ
 3335                const_function(),
 3336                ˇ
 3337                ˇ
 3338                something_else,
 3339                ˇ
 3340                ˇ
 3341                ˇ
 3342                ˇ
 3343            )
 3344            ˇ
 3345        );
 3346        ˇ
 3347        ˇ
 3348    "});
 3349}
 3350
 3351#[gpui::test]
 3352async fn test_newline_comments(cx: &mut TestAppContext) {
 3353    init_test(cx, |settings| {
 3354        settings.defaults.tab_size = NonZeroU32::new(4)
 3355    });
 3356
 3357    let language = Arc::new(Language::new(
 3358        LanguageConfig {
 3359            line_comments: vec!["// ".into()],
 3360            ..LanguageConfig::default()
 3361        },
 3362        None,
 3363    ));
 3364    {
 3365        let mut cx = EditorTestContext::new(cx).await;
 3366        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3367        cx.set_state(indoc! {"
 3368        // Fooˇ
 3369    "});
 3370
 3371        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3372        cx.assert_editor_state(indoc! {"
 3373        // Foo
 3374        // ˇ
 3375    "});
 3376        // Ensure that we add comment prefix when existing line contains space
 3377        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3378        cx.assert_editor_state(
 3379            indoc! {"
 3380        // Foo
 3381        //s
 3382        // ˇ
 3383    "}
 3384            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3385            .as_str(),
 3386        );
 3387        // Ensure that we add comment prefix when existing line does not contain space
 3388        cx.set_state(indoc! {"
 3389        // Foo
 3390        //ˇ
 3391    "});
 3392        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3393        cx.assert_editor_state(indoc! {"
 3394        // Foo
 3395        //
 3396        // ˇ
 3397    "});
 3398        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3399        cx.set_state(indoc! {"
 3400        ˇ// Foo
 3401    "});
 3402        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3403        cx.assert_editor_state(indoc! {"
 3404
 3405        ˇ// Foo
 3406    "});
 3407    }
 3408    // Ensure that comment continuations can be disabled.
 3409    update_test_language_settings(cx, |settings| {
 3410        settings.defaults.extend_comment_on_newline = Some(false);
 3411    });
 3412    let mut cx = EditorTestContext::new(cx).await;
 3413    cx.set_state(indoc! {"
 3414        // Fooˇ
 3415    "});
 3416    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3417    cx.assert_editor_state(indoc! {"
 3418        // Foo
 3419        ˇ
 3420    "});
 3421}
 3422
 3423#[gpui::test]
 3424async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3425    init_test(cx, |settings| {
 3426        settings.defaults.tab_size = NonZeroU32::new(4)
 3427    });
 3428
 3429    let language = Arc::new(Language::new(
 3430        LanguageConfig {
 3431            line_comments: vec!["// ".into(), "/// ".into()],
 3432            ..LanguageConfig::default()
 3433        },
 3434        None,
 3435    ));
 3436    {
 3437        let mut cx = EditorTestContext::new(cx).await;
 3438        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3439        cx.set_state(indoc! {"
 3440        //ˇ
 3441    "});
 3442        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3443        cx.assert_editor_state(indoc! {"
 3444        //
 3445        // ˇ
 3446    "});
 3447
 3448        cx.set_state(indoc! {"
 3449        ///ˇ
 3450    "});
 3451        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3452        cx.assert_editor_state(indoc! {"
 3453        ///
 3454        /// ˇ
 3455    "});
 3456    }
 3457}
 3458
 3459#[gpui::test]
 3460async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3461    init_test(cx, |settings| {
 3462        settings.defaults.tab_size = NonZeroU32::new(4)
 3463    });
 3464
 3465    let language = Arc::new(
 3466        Language::new(
 3467            LanguageConfig {
 3468                documentation_comment: Some(language::BlockCommentConfig {
 3469                    start: "/**".into(),
 3470                    end: "*/".into(),
 3471                    prefix: "* ".into(),
 3472                    tab_size: 1,
 3473                }),
 3474
 3475                ..LanguageConfig::default()
 3476            },
 3477            Some(tree_sitter_rust::LANGUAGE.into()),
 3478        )
 3479        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3480        .unwrap(),
 3481    );
 3482
 3483    {
 3484        let mut cx = EditorTestContext::new(cx).await;
 3485        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3486        cx.set_state(indoc! {"
 3487        /**ˇ
 3488    "});
 3489
 3490        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3491        cx.assert_editor_state(indoc! {"
 3492        /**
 3493         * ˇ
 3494    "});
 3495        // Ensure that if cursor is before the comment start,
 3496        // we do not actually insert a comment prefix.
 3497        cx.set_state(indoc! {"
 3498        ˇ/**
 3499    "});
 3500        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3501        cx.assert_editor_state(indoc! {"
 3502
 3503        ˇ/**
 3504    "});
 3505        // Ensure that if cursor is between it doesn't add comment prefix.
 3506        cx.set_state(indoc! {"
 3507        /*ˇ*
 3508    "});
 3509        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3510        cx.assert_editor_state(indoc! {"
 3511        /*
 3512        ˇ*
 3513    "});
 3514        // Ensure that if suffix exists on same line after cursor it adds new line.
 3515        cx.set_state(indoc! {"
 3516        /**ˇ*/
 3517    "});
 3518        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3519        cx.assert_editor_state(indoc! {"
 3520        /**
 3521         * ˇ
 3522         */
 3523    "});
 3524        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3525        cx.set_state(indoc! {"
 3526        /**ˇ */
 3527    "});
 3528        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3529        cx.assert_editor_state(indoc! {"
 3530        /**
 3531         * ˇ
 3532         */
 3533    "});
 3534        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3535        cx.set_state(indoc! {"
 3536        /** ˇ*/
 3537    "});
 3538        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3539        cx.assert_editor_state(
 3540            indoc! {"
 3541        /**s
 3542         * ˇ
 3543         */
 3544    "}
 3545            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3546            .as_str(),
 3547        );
 3548        // Ensure that delimiter space is preserved when newline on already
 3549        // spaced delimiter.
 3550        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3551        cx.assert_editor_state(
 3552            indoc! {"
 3553        /**s
 3554         *s
 3555         * ˇ
 3556         */
 3557    "}
 3558            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3559            .as_str(),
 3560        );
 3561        // Ensure that delimiter space is preserved when space is not
 3562        // on existing delimiter.
 3563        cx.set_state(indoc! {"
 3564        /**
 3565 3566         */
 3567    "});
 3568        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3569        cx.assert_editor_state(indoc! {"
 3570        /**
 3571         *
 3572         * ˇ
 3573         */
 3574    "});
 3575        // Ensure that if suffix exists on same line after cursor it
 3576        // doesn't add extra new line if prefix is not on same line.
 3577        cx.set_state(indoc! {"
 3578        /**
 3579        ˇ*/
 3580    "});
 3581        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3582        cx.assert_editor_state(indoc! {"
 3583        /**
 3584
 3585        ˇ*/
 3586    "});
 3587        // Ensure that it detects suffix after existing prefix.
 3588        cx.set_state(indoc! {"
 3589        /**ˇ/
 3590    "});
 3591        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3592        cx.assert_editor_state(indoc! {"
 3593        /**
 3594        ˇ/
 3595    "});
 3596        // Ensure that if suffix exists on same line before
 3597        // cursor it does not add comment prefix.
 3598        cx.set_state(indoc! {"
 3599        /** */ˇ
 3600    "});
 3601        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3602        cx.assert_editor_state(indoc! {"
 3603        /** */
 3604        ˇ
 3605    "});
 3606        // Ensure that if suffix exists on same line before
 3607        // cursor it does not add comment prefix.
 3608        cx.set_state(indoc! {"
 3609        /**
 3610         *
 3611         */ˇ
 3612    "});
 3613        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3614        cx.assert_editor_state(indoc! {"
 3615        /**
 3616         *
 3617         */
 3618         ˇ
 3619    "});
 3620
 3621        // Ensure that inline comment followed by code
 3622        // doesn't add comment prefix on newline
 3623        cx.set_state(indoc! {"
 3624        /** */ textˇ
 3625    "});
 3626        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3627        cx.assert_editor_state(indoc! {"
 3628        /** */ text
 3629        ˇ
 3630    "});
 3631
 3632        // Ensure that text after comment end tag
 3633        // doesn't add comment prefix on newline
 3634        cx.set_state(indoc! {"
 3635        /**
 3636         *
 3637         */ˇtext
 3638    "});
 3639        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3640        cx.assert_editor_state(indoc! {"
 3641        /**
 3642         *
 3643         */
 3644         ˇtext
 3645    "});
 3646
 3647        // Ensure if not comment block it doesn't
 3648        // add comment prefix on newline
 3649        cx.set_state(indoc! {"
 3650        * textˇ
 3651    "});
 3652        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3653        cx.assert_editor_state(indoc! {"
 3654        * text
 3655        ˇ
 3656    "});
 3657    }
 3658    // Ensure that comment continuations can be disabled.
 3659    update_test_language_settings(cx, |settings| {
 3660        settings.defaults.extend_comment_on_newline = Some(false);
 3661    });
 3662    let mut cx = EditorTestContext::new(cx).await;
 3663    cx.set_state(indoc! {"
 3664        /**ˇ
 3665    "});
 3666    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3667    cx.assert_editor_state(indoc! {"
 3668        /**
 3669        ˇ
 3670    "});
 3671}
 3672
 3673#[gpui::test]
 3674async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3675    init_test(cx, |settings| {
 3676        settings.defaults.tab_size = NonZeroU32::new(4)
 3677    });
 3678
 3679    let lua_language = Arc::new(Language::new(
 3680        LanguageConfig {
 3681            line_comments: vec!["--".into()],
 3682            block_comment: Some(language::BlockCommentConfig {
 3683                start: "--[[".into(),
 3684                prefix: "".into(),
 3685                end: "]]".into(),
 3686                tab_size: 0,
 3687            }),
 3688            ..LanguageConfig::default()
 3689        },
 3690        None,
 3691    ));
 3692
 3693    let mut cx = EditorTestContext::new(cx).await;
 3694    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3695
 3696    // Line with line comment should extend
 3697    cx.set_state(indoc! {"
 3698        --ˇ
 3699    "});
 3700    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3701    cx.assert_editor_state(indoc! {"
 3702        --
 3703        --ˇ
 3704    "});
 3705
 3706    // Line with block comment that matches line comment should not extend
 3707    cx.set_state(indoc! {"
 3708        --[[ˇ
 3709    "});
 3710    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3711    cx.assert_editor_state(indoc! {"
 3712        --[[
 3713        ˇ
 3714    "});
 3715}
 3716
 3717#[gpui::test]
 3718fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3719    init_test(cx, |_| {});
 3720
 3721    let editor = cx.add_window(|window, cx| {
 3722        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3723        let mut editor = build_editor(buffer, window, cx);
 3724        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3725            s.select_ranges([
 3726                MultiBufferOffset(3)..MultiBufferOffset(4),
 3727                MultiBufferOffset(11)..MultiBufferOffset(12),
 3728                MultiBufferOffset(19)..MultiBufferOffset(20),
 3729            ])
 3730        });
 3731        editor
 3732    });
 3733
 3734    _ = editor.update(cx, |editor, window, cx| {
 3735        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3736        editor.buffer.update(cx, |buffer, cx| {
 3737            buffer.edit(
 3738                [
 3739                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3740                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3741                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3742                ],
 3743                None,
 3744                cx,
 3745            );
 3746            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3747        });
 3748        assert_eq!(
 3749            editor.selections.ranges(&editor.display_snapshot(cx)),
 3750            &[
 3751                MultiBufferOffset(2)..MultiBufferOffset(2),
 3752                MultiBufferOffset(7)..MultiBufferOffset(7),
 3753                MultiBufferOffset(12)..MultiBufferOffset(12)
 3754            ],
 3755        );
 3756
 3757        editor.insert("Z", window, cx);
 3758        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3759
 3760        // The selections are moved after the inserted characters
 3761        assert_eq!(
 3762            editor.selections.ranges(&editor.display_snapshot(cx)),
 3763            &[
 3764                MultiBufferOffset(3)..MultiBufferOffset(3),
 3765                MultiBufferOffset(9)..MultiBufferOffset(9),
 3766                MultiBufferOffset(15)..MultiBufferOffset(15)
 3767            ],
 3768        );
 3769    });
 3770}
 3771
 3772#[gpui::test]
 3773async fn test_tab(cx: &mut TestAppContext) {
 3774    init_test(cx, |settings| {
 3775        settings.defaults.tab_size = NonZeroU32::new(3)
 3776    });
 3777
 3778    let mut cx = EditorTestContext::new(cx).await;
 3779    cx.set_state(indoc! {"
 3780        ˇabˇc
 3781        ˇ🏀ˇ🏀ˇefg
 3782 3783    "});
 3784    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3785    cx.assert_editor_state(indoc! {"
 3786           ˇab ˇc
 3787           ˇ🏀  ˇ🏀  ˇefg
 3788        d  ˇ
 3789    "});
 3790
 3791    cx.set_state(indoc! {"
 3792        a
 3793        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3794    "});
 3795    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3796    cx.assert_editor_state(indoc! {"
 3797        a
 3798           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3799    "});
 3800}
 3801
 3802#[gpui::test]
 3803async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3804    init_test(cx, |_| {});
 3805
 3806    let mut cx = EditorTestContext::new(cx).await;
 3807    let language = Arc::new(
 3808        Language::new(
 3809            LanguageConfig::default(),
 3810            Some(tree_sitter_rust::LANGUAGE.into()),
 3811        )
 3812        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3813        .unwrap(),
 3814    );
 3815    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3816
 3817    // test when all cursors are not at suggested indent
 3818    // then simply move to their suggested indent location
 3819    cx.set_state(indoc! {"
 3820        const a: B = (
 3821            c(
 3822        ˇ
 3823        ˇ    )
 3824        );
 3825    "});
 3826    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3827    cx.assert_editor_state(indoc! {"
 3828        const a: B = (
 3829            c(
 3830                ˇ
 3831            ˇ)
 3832        );
 3833    "});
 3834
 3835    // test cursor already at suggested indent not moving when
 3836    // other cursors are yet to reach their suggested indents
 3837    cx.set_state(indoc! {"
 3838        ˇ
 3839        const a: B = (
 3840            c(
 3841                d(
 3842        ˇ
 3843                )
 3844        ˇ
 3845        ˇ    )
 3846        );
 3847    "});
 3848    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3849    cx.assert_editor_state(indoc! {"
 3850        ˇ
 3851        const a: B = (
 3852            c(
 3853                d(
 3854                    ˇ
 3855                )
 3856                ˇ
 3857            ˇ)
 3858        );
 3859    "});
 3860    // test when all cursors are at suggested indent then tab is inserted
 3861    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3862    cx.assert_editor_state(indoc! {"
 3863            ˇ
 3864        const a: B = (
 3865            c(
 3866                d(
 3867                        ˇ
 3868                )
 3869                    ˇ
 3870                ˇ)
 3871        );
 3872    "});
 3873
 3874    // test when current indent is less than suggested indent,
 3875    // we adjust line to match suggested indent and move cursor to it
 3876    //
 3877    // when no other cursor is at word boundary, all of them should move
 3878    cx.set_state(indoc! {"
 3879        const a: B = (
 3880            c(
 3881                d(
 3882        ˇ
 3883        ˇ   )
 3884        ˇ   )
 3885        );
 3886    "});
 3887    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3888    cx.assert_editor_state(indoc! {"
 3889        const a: B = (
 3890            c(
 3891                d(
 3892                    ˇ
 3893                ˇ)
 3894            ˇ)
 3895        );
 3896    "});
 3897
 3898    // test when current indent is less than suggested indent,
 3899    // we adjust line to match suggested indent and move cursor to it
 3900    //
 3901    // when some other cursor is at word boundary, it should not move
 3902    cx.set_state(indoc! {"
 3903        const a: B = (
 3904            c(
 3905                d(
 3906        ˇ
 3907        ˇ   )
 3908           ˇ)
 3909        );
 3910    "});
 3911    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3912    cx.assert_editor_state(indoc! {"
 3913        const a: B = (
 3914            c(
 3915                d(
 3916                    ˇ
 3917                ˇ)
 3918            ˇ)
 3919        );
 3920    "});
 3921
 3922    // test when current indent is more than suggested indent,
 3923    // we just move cursor to current indent instead of suggested indent
 3924    //
 3925    // when no other cursor is at word boundary, all of them should move
 3926    cx.set_state(indoc! {"
 3927        const a: B = (
 3928            c(
 3929                d(
 3930        ˇ
 3931        ˇ                )
 3932        ˇ   )
 3933        );
 3934    "});
 3935    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3936    cx.assert_editor_state(indoc! {"
 3937        const a: B = (
 3938            c(
 3939                d(
 3940                    ˇ
 3941                        ˇ)
 3942            ˇ)
 3943        );
 3944    "});
 3945    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3946    cx.assert_editor_state(indoc! {"
 3947        const a: B = (
 3948            c(
 3949                d(
 3950                        ˇ
 3951                            ˇ)
 3952                ˇ)
 3953        );
 3954    "});
 3955
 3956    // test when current indent is more than suggested indent,
 3957    // we just move cursor to current indent instead of suggested indent
 3958    //
 3959    // when some other cursor is at word boundary, it doesn't move
 3960    cx.set_state(indoc! {"
 3961        const a: B = (
 3962            c(
 3963                d(
 3964        ˇ
 3965        ˇ                )
 3966            ˇ)
 3967        );
 3968    "});
 3969    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3970    cx.assert_editor_state(indoc! {"
 3971        const a: B = (
 3972            c(
 3973                d(
 3974                    ˇ
 3975                        ˇ)
 3976            ˇ)
 3977        );
 3978    "});
 3979
 3980    // handle auto-indent when there are multiple cursors on the same line
 3981    cx.set_state(indoc! {"
 3982        const a: B = (
 3983            c(
 3984        ˇ    ˇ
 3985        ˇ    )
 3986        );
 3987    "});
 3988    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3989    cx.assert_editor_state(indoc! {"
 3990        const a: B = (
 3991            c(
 3992                ˇ
 3993            ˇ)
 3994        );
 3995    "});
 3996}
 3997
 3998#[gpui::test]
 3999async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4000    init_test(cx, |settings| {
 4001        settings.defaults.tab_size = NonZeroU32::new(3)
 4002    });
 4003
 4004    let mut cx = EditorTestContext::new(cx).await;
 4005    cx.set_state(indoc! {"
 4006         ˇ
 4007        \t ˇ
 4008        \t  ˇ
 4009        \t   ˇ
 4010         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4011    "});
 4012
 4013    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4014    cx.assert_editor_state(indoc! {"
 4015           ˇ
 4016        \t   ˇ
 4017        \t   ˇ
 4018        \t      ˇ
 4019         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4020    "});
 4021}
 4022
 4023#[gpui::test]
 4024async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4025    init_test(cx, |settings| {
 4026        settings.defaults.tab_size = NonZeroU32::new(4)
 4027    });
 4028
 4029    let language = Arc::new(
 4030        Language::new(
 4031            LanguageConfig::default(),
 4032            Some(tree_sitter_rust::LANGUAGE.into()),
 4033        )
 4034        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4035        .unwrap(),
 4036    );
 4037
 4038    let mut cx = EditorTestContext::new(cx).await;
 4039    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4040    cx.set_state(indoc! {"
 4041        fn a() {
 4042            if b {
 4043        \t ˇc
 4044            }
 4045        }
 4046    "});
 4047
 4048    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4049    cx.assert_editor_state(indoc! {"
 4050        fn a() {
 4051            if b {
 4052                ˇc
 4053            }
 4054        }
 4055    "});
 4056}
 4057
 4058#[gpui::test]
 4059async fn test_indent_outdent(cx: &mut TestAppContext) {
 4060    init_test(cx, |settings| {
 4061        settings.defaults.tab_size = NonZeroU32::new(4);
 4062    });
 4063
 4064    let mut cx = EditorTestContext::new(cx).await;
 4065
 4066    cx.set_state(indoc! {"
 4067          «oneˇ» «twoˇ»
 4068        three
 4069         four
 4070    "});
 4071    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4072    cx.assert_editor_state(indoc! {"
 4073            «oneˇ» «twoˇ»
 4074        three
 4075         four
 4076    "});
 4077
 4078    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4079    cx.assert_editor_state(indoc! {"
 4080        «oneˇ» «twoˇ»
 4081        three
 4082         four
 4083    "});
 4084
 4085    // select across line ending
 4086    cx.set_state(indoc! {"
 4087        one two
 4088        t«hree
 4089        ˇ» four
 4090    "});
 4091    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4092    cx.assert_editor_state(indoc! {"
 4093        one two
 4094            t«hree
 4095        ˇ» four
 4096    "});
 4097
 4098    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4099    cx.assert_editor_state(indoc! {"
 4100        one two
 4101        t«hree
 4102        ˇ» four
 4103    "});
 4104
 4105    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4106    cx.set_state(indoc! {"
 4107        one two
 4108        ˇthree
 4109            four
 4110    "});
 4111    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4112    cx.assert_editor_state(indoc! {"
 4113        one two
 4114            ˇthree
 4115            four
 4116    "});
 4117
 4118    cx.set_state(indoc! {"
 4119        one two
 4120        ˇ    three
 4121            four
 4122    "});
 4123    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4124    cx.assert_editor_state(indoc! {"
 4125        one two
 4126        ˇthree
 4127            four
 4128    "});
 4129}
 4130
 4131#[gpui::test]
 4132async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4133    // This is a regression test for issue #33761
 4134    init_test(cx, |_| {});
 4135
 4136    let mut cx = EditorTestContext::new(cx).await;
 4137    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4138    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4139
 4140    cx.set_state(
 4141        r#"ˇ#     ingress:
 4142ˇ#         api:
 4143ˇ#             enabled: false
 4144ˇ#             pathType: Prefix
 4145ˇ#           console:
 4146ˇ#               enabled: false
 4147ˇ#               pathType: Prefix
 4148"#,
 4149    );
 4150
 4151    // Press tab to indent all lines
 4152    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4153
 4154    cx.assert_editor_state(
 4155        r#"    ˇ#     ingress:
 4156    ˇ#         api:
 4157    ˇ#             enabled: false
 4158    ˇ#             pathType: Prefix
 4159    ˇ#           console:
 4160    ˇ#               enabled: false
 4161    ˇ#               pathType: Prefix
 4162"#,
 4163    );
 4164}
 4165
 4166#[gpui::test]
 4167async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4168    // This is a test to make sure our fix for issue #33761 didn't break anything
 4169    init_test(cx, |_| {});
 4170
 4171    let mut cx = EditorTestContext::new(cx).await;
 4172    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4173    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4174
 4175    cx.set_state(
 4176        r#"ˇingress:
 4177ˇ  api:
 4178ˇ    enabled: false
 4179ˇ    pathType: Prefix
 4180"#,
 4181    );
 4182
 4183    // Press tab to indent all lines
 4184    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4185
 4186    cx.assert_editor_state(
 4187        r#"ˇingress:
 4188    ˇapi:
 4189        ˇenabled: false
 4190        ˇpathType: Prefix
 4191"#,
 4192    );
 4193}
 4194
 4195#[gpui::test]
 4196async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4197    init_test(cx, |settings| {
 4198        settings.defaults.hard_tabs = Some(true);
 4199    });
 4200
 4201    let mut cx = EditorTestContext::new(cx).await;
 4202
 4203    // select two ranges on one line
 4204    cx.set_state(indoc! {"
 4205        «oneˇ» «twoˇ»
 4206        three
 4207        four
 4208    "});
 4209    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4210    cx.assert_editor_state(indoc! {"
 4211        \t«oneˇ» «twoˇ»
 4212        three
 4213        four
 4214    "});
 4215    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4216    cx.assert_editor_state(indoc! {"
 4217        \t\t«oneˇ» «twoˇ»
 4218        three
 4219        four
 4220    "});
 4221    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4222    cx.assert_editor_state(indoc! {"
 4223        \t«oneˇ» «twoˇ»
 4224        three
 4225        four
 4226    "});
 4227    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4228    cx.assert_editor_state(indoc! {"
 4229        «oneˇ» «twoˇ»
 4230        three
 4231        four
 4232    "});
 4233
 4234    // select across a line ending
 4235    cx.set_state(indoc! {"
 4236        one two
 4237        t«hree
 4238        ˇ»four
 4239    "});
 4240    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4241    cx.assert_editor_state(indoc! {"
 4242        one two
 4243        \tt«hree
 4244        ˇ»four
 4245    "});
 4246    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4247    cx.assert_editor_state(indoc! {"
 4248        one two
 4249        \t\tt«hree
 4250        ˇ»four
 4251    "});
 4252    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4253    cx.assert_editor_state(indoc! {"
 4254        one two
 4255        \tt«hree
 4256        ˇ»four
 4257    "});
 4258    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4259    cx.assert_editor_state(indoc! {"
 4260        one two
 4261        t«hree
 4262        ˇ»four
 4263    "});
 4264
 4265    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4266    cx.set_state(indoc! {"
 4267        one two
 4268        ˇthree
 4269        four
 4270    "});
 4271    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4272    cx.assert_editor_state(indoc! {"
 4273        one two
 4274        ˇthree
 4275        four
 4276    "});
 4277    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4278    cx.assert_editor_state(indoc! {"
 4279        one two
 4280        \tˇthree
 4281        four
 4282    "});
 4283    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4284    cx.assert_editor_state(indoc! {"
 4285        one two
 4286        ˇthree
 4287        four
 4288    "});
 4289}
 4290
 4291#[gpui::test]
 4292fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4293    init_test(cx, |settings| {
 4294        settings.languages.0.extend([
 4295            (
 4296                "TOML".into(),
 4297                LanguageSettingsContent {
 4298                    tab_size: NonZeroU32::new(2),
 4299                    ..Default::default()
 4300                },
 4301            ),
 4302            (
 4303                "Rust".into(),
 4304                LanguageSettingsContent {
 4305                    tab_size: NonZeroU32::new(4),
 4306                    ..Default::default()
 4307                },
 4308            ),
 4309        ]);
 4310    });
 4311
 4312    let toml_language = Arc::new(Language::new(
 4313        LanguageConfig {
 4314            name: "TOML".into(),
 4315            ..Default::default()
 4316        },
 4317        None,
 4318    ));
 4319    let rust_language = Arc::new(Language::new(
 4320        LanguageConfig {
 4321            name: "Rust".into(),
 4322            ..Default::default()
 4323        },
 4324        None,
 4325    ));
 4326
 4327    let toml_buffer =
 4328        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4329    let rust_buffer =
 4330        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4331    let multibuffer = cx.new(|cx| {
 4332        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4333        multibuffer.push_excerpts(
 4334            toml_buffer.clone(),
 4335            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4336            cx,
 4337        );
 4338        multibuffer.push_excerpts(
 4339            rust_buffer.clone(),
 4340            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4341            cx,
 4342        );
 4343        multibuffer
 4344    });
 4345
 4346    cx.add_window(|window, cx| {
 4347        let mut editor = build_editor(multibuffer, window, cx);
 4348
 4349        assert_eq!(
 4350            editor.text(cx),
 4351            indoc! {"
 4352                a = 1
 4353                b = 2
 4354
 4355                const c: usize = 3;
 4356            "}
 4357        );
 4358
 4359        select_ranges(
 4360            &mut editor,
 4361            indoc! {"
 4362                «aˇ» = 1
 4363                b = 2
 4364
 4365                «const c:ˇ» usize = 3;
 4366            "},
 4367            window,
 4368            cx,
 4369        );
 4370
 4371        editor.tab(&Tab, window, cx);
 4372        assert_text_with_selections(
 4373            &mut editor,
 4374            indoc! {"
 4375                  «aˇ» = 1
 4376                b = 2
 4377
 4378                    «const c:ˇ» usize = 3;
 4379            "},
 4380            cx,
 4381        );
 4382        editor.backtab(&Backtab, window, cx);
 4383        assert_text_with_selections(
 4384            &mut editor,
 4385            indoc! {"
 4386                «aˇ» = 1
 4387                b = 2
 4388
 4389                «const c:ˇ» usize = 3;
 4390            "},
 4391            cx,
 4392        );
 4393
 4394        editor
 4395    });
 4396}
 4397
 4398#[gpui::test]
 4399async fn test_backspace(cx: &mut TestAppContext) {
 4400    init_test(cx, |_| {});
 4401
 4402    let mut cx = EditorTestContext::new(cx).await;
 4403
 4404    // Basic backspace
 4405    cx.set_state(indoc! {"
 4406        onˇe two three
 4407        fou«rˇ» five six
 4408        seven «ˇeight nine
 4409        »ten
 4410    "});
 4411    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4412    cx.assert_editor_state(indoc! {"
 4413        oˇe two three
 4414        fouˇ five six
 4415        seven ˇten
 4416    "});
 4417
 4418    // Test backspace inside and around indents
 4419    cx.set_state(indoc! {"
 4420        zero
 4421            ˇone
 4422                ˇtwo
 4423            ˇ ˇ ˇ  three
 4424        ˇ  ˇ  four
 4425    "});
 4426    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4427    cx.assert_editor_state(indoc! {"
 4428        zero
 4429        ˇone
 4430            ˇtwo
 4431        ˇ  threeˇ  four
 4432    "});
 4433}
 4434
 4435#[gpui::test]
 4436async fn test_delete(cx: &mut TestAppContext) {
 4437    init_test(cx, |_| {});
 4438
 4439    let mut cx = EditorTestContext::new(cx).await;
 4440    cx.set_state(indoc! {"
 4441        onˇe two three
 4442        fou«rˇ» five six
 4443        seven «ˇeight nine
 4444        »ten
 4445    "});
 4446    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4447    cx.assert_editor_state(indoc! {"
 4448        onˇ two three
 4449        fouˇ five six
 4450        seven ˇten
 4451    "});
 4452}
 4453
 4454#[gpui::test]
 4455fn test_delete_line(cx: &mut TestAppContext) {
 4456    init_test(cx, |_| {});
 4457
 4458    let editor = cx.add_window(|window, cx| {
 4459        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4460        build_editor(buffer, window, cx)
 4461    });
 4462    _ = editor.update(cx, |editor, window, cx| {
 4463        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4464            s.select_display_ranges([
 4465                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4466                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4467                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4468            ])
 4469        });
 4470        editor.delete_line(&DeleteLine, window, cx);
 4471        assert_eq!(editor.display_text(cx), "ghi");
 4472        assert_eq!(
 4473            display_ranges(editor, cx),
 4474            vec![
 4475                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4476                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4477            ]
 4478        );
 4479    });
 4480
 4481    let editor = cx.add_window(|window, cx| {
 4482        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4483        build_editor(buffer, window, cx)
 4484    });
 4485    _ = editor.update(cx, |editor, window, cx| {
 4486        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4487            s.select_display_ranges([
 4488                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4489            ])
 4490        });
 4491        editor.delete_line(&DeleteLine, window, cx);
 4492        assert_eq!(editor.display_text(cx), "ghi\n");
 4493        assert_eq!(
 4494            display_ranges(editor, cx),
 4495            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4496        );
 4497    });
 4498
 4499    let editor = cx.add_window(|window, cx| {
 4500        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4501        build_editor(buffer, window, cx)
 4502    });
 4503    _ = editor.update(cx, |editor, window, cx| {
 4504        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4505            s.select_display_ranges([
 4506                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4507            ])
 4508        });
 4509        editor.delete_line(&DeleteLine, window, cx);
 4510        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4511        assert_eq!(
 4512            display_ranges(editor, cx),
 4513            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4514        );
 4515    });
 4516}
 4517
 4518#[gpui::test]
 4519fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4520    init_test(cx, |_| {});
 4521
 4522    cx.add_window(|window, cx| {
 4523        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4524        let mut editor = build_editor(buffer.clone(), window, cx);
 4525        let buffer = buffer.read(cx).as_singleton().unwrap();
 4526
 4527        assert_eq!(
 4528            editor
 4529                .selections
 4530                .ranges::<Point>(&editor.display_snapshot(cx)),
 4531            &[Point::new(0, 0)..Point::new(0, 0)]
 4532        );
 4533
 4534        // When on single line, replace newline at end by space
 4535        editor.join_lines(&JoinLines, window, cx);
 4536        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4537        assert_eq!(
 4538            editor
 4539                .selections
 4540                .ranges::<Point>(&editor.display_snapshot(cx)),
 4541            &[Point::new(0, 3)..Point::new(0, 3)]
 4542        );
 4543
 4544        // When multiple lines are selected, remove newlines that are spanned by the selection
 4545        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4546            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4547        });
 4548        editor.join_lines(&JoinLines, window, cx);
 4549        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4550        assert_eq!(
 4551            editor
 4552                .selections
 4553                .ranges::<Point>(&editor.display_snapshot(cx)),
 4554            &[Point::new(0, 11)..Point::new(0, 11)]
 4555        );
 4556
 4557        // Undo should be transactional
 4558        editor.undo(&Undo, window, cx);
 4559        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4560        assert_eq!(
 4561            editor
 4562                .selections
 4563                .ranges::<Point>(&editor.display_snapshot(cx)),
 4564            &[Point::new(0, 5)..Point::new(2, 2)]
 4565        );
 4566
 4567        // When joining an empty line don't insert a space
 4568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4569            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4570        });
 4571        editor.join_lines(&JoinLines, window, cx);
 4572        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4573        assert_eq!(
 4574            editor
 4575                .selections
 4576                .ranges::<Point>(&editor.display_snapshot(cx)),
 4577            [Point::new(2, 3)..Point::new(2, 3)]
 4578        );
 4579
 4580        // We can remove trailing newlines
 4581        editor.join_lines(&JoinLines, window, cx);
 4582        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4583        assert_eq!(
 4584            editor
 4585                .selections
 4586                .ranges::<Point>(&editor.display_snapshot(cx)),
 4587            [Point::new(2, 3)..Point::new(2, 3)]
 4588        );
 4589
 4590        // We don't blow up on the last line
 4591        editor.join_lines(&JoinLines, window, cx);
 4592        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4593        assert_eq!(
 4594            editor
 4595                .selections
 4596                .ranges::<Point>(&editor.display_snapshot(cx)),
 4597            [Point::new(2, 3)..Point::new(2, 3)]
 4598        );
 4599
 4600        // reset to test indentation
 4601        editor.buffer.update(cx, |buffer, cx| {
 4602            buffer.edit(
 4603                [
 4604                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4605                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4606                ],
 4607                None,
 4608                cx,
 4609            )
 4610        });
 4611
 4612        // We remove any leading spaces
 4613        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4614        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4615            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4616        });
 4617        editor.join_lines(&JoinLines, window, cx);
 4618        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4619
 4620        // We don't insert a space for a line containing only spaces
 4621        editor.join_lines(&JoinLines, window, cx);
 4622        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4623
 4624        // We ignore any leading tabs
 4625        editor.join_lines(&JoinLines, window, cx);
 4626        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4627
 4628        editor
 4629    });
 4630}
 4631
 4632#[gpui::test]
 4633fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4634    init_test(cx, |_| {});
 4635
 4636    cx.add_window(|window, cx| {
 4637        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4638        let mut editor = build_editor(buffer.clone(), window, cx);
 4639        let buffer = buffer.read(cx).as_singleton().unwrap();
 4640
 4641        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4642            s.select_ranges([
 4643                Point::new(0, 2)..Point::new(1, 1),
 4644                Point::new(1, 2)..Point::new(1, 2),
 4645                Point::new(3, 1)..Point::new(3, 2),
 4646            ])
 4647        });
 4648
 4649        editor.join_lines(&JoinLines, window, cx);
 4650        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4651
 4652        assert_eq!(
 4653            editor
 4654                .selections
 4655                .ranges::<Point>(&editor.display_snapshot(cx)),
 4656            [
 4657                Point::new(0, 7)..Point::new(0, 7),
 4658                Point::new(1, 3)..Point::new(1, 3)
 4659            ]
 4660        );
 4661        editor
 4662    });
 4663}
 4664
 4665#[gpui::test]
 4666async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4667    init_test(cx, |_| {});
 4668
 4669    let mut cx = EditorTestContext::new(cx).await;
 4670
 4671    let diff_base = r#"
 4672        Line 0
 4673        Line 1
 4674        Line 2
 4675        Line 3
 4676        "#
 4677    .unindent();
 4678
 4679    cx.set_state(
 4680        &r#"
 4681        ˇLine 0
 4682        Line 1
 4683        Line 2
 4684        Line 3
 4685        "#
 4686        .unindent(),
 4687    );
 4688
 4689    cx.set_head_text(&diff_base);
 4690    executor.run_until_parked();
 4691
 4692    // Join lines
 4693    cx.update_editor(|editor, window, cx| {
 4694        editor.join_lines(&JoinLines, window, cx);
 4695    });
 4696    executor.run_until_parked();
 4697
 4698    cx.assert_editor_state(
 4699        &r#"
 4700        Line 0ˇ Line 1
 4701        Line 2
 4702        Line 3
 4703        "#
 4704        .unindent(),
 4705    );
 4706    // Join again
 4707    cx.update_editor(|editor, window, cx| {
 4708        editor.join_lines(&JoinLines, window, cx);
 4709    });
 4710    executor.run_until_parked();
 4711
 4712    cx.assert_editor_state(
 4713        &r#"
 4714        Line 0 Line 1ˇ Line 2
 4715        Line 3
 4716        "#
 4717        .unindent(),
 4718    );
 4719}
 4720
 4721#[gpui::test]
 4722async fn test_custom_newlines_cause_no_false_positive_diffs(
 4723    executor: BackgroundExecutor,
 4724    cx: &mut TestAppContext,
 4725) {
 4726    init_test(cx, |_| {});
 4727    let mut cx = EditorTestContext::new(cx).await;
 4728    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4729    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4730    executor.run_until_parked();
 4731
 4732    cx.update_editor(|editor, window, cx| {
 4733        let snapshot = editor.snapshot(window, cx);
 4734        assert_eq!(
 4735            snapshot
 4736                .buffer_snapshot()
 4737                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4738                .collect::<Vec<_>>(),
 4739            Vec::new(),
 4740            "Should not have any diffs for files with custom newlines"
 4741        );
 4742    });
 4743}
 4744
 4745#[gpui::test]
 4746async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4747    init_test(cx, |_| {});
 4748
 4749    let mut cx = EditorTestContext::new(cx).await;
 4750
 4751    // Test sort_lines_case_insensitive()
 4752    cx.set_state(indoc! {"
 4753        «z
 4754        y
 4755        x
 4756        Z
 4757        Y
 4758        Xˇ»
 4759    "});
 4760    cx.update_editor(|e, window, cx| {
 4761        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4762    });
 4763    cx.assert_editor_state(indoc! {"
 4764        «x
 4765        X
 4766        y
 4767        Y
 4768        z
 4769        Zˇ»
 4770    "});
 4771
 4772    // Test sort_lines_by_length()
 4773    //
 4774    // Demonstrates:
 4775    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4776    // - sort is stable
 4777    cx.set_state(indoc! {"
 4778        «123
 4779        æ
 4780        12
 4781 4782        1
 4783        æˇ»
 4784    "});
 4785    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4786    cx.assert_editor_state(indoc! {"
 4787        «æ
 4788 4789        1
 4790        æ
 4791        12
 4792        123ˇ»
 4793    "});
 4794
 4795    // Test reverse_lines()
 4796    cx.set_state(indoc! {"
 4797        «5
 4798        4
 4799        3
 4800        2
 4801        1ˇ»
 4802    "});
 4803    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4804    cx.assert_editor_state(indoc! {"
 4805        «1
 4806        2
 4807        3
 4808        4
 4809        5ˇ»
 4810    "});
 4811
 4812    // Skip testing shuffle_line()
 4813
 4814    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4815    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4816
 4817    // Don't manipulate when cursor is on single line, but expand the selection
 4818    cx.set_state(indoc! {"
 4819        ddˇdd
 4820        ccc
 4821        bb
 4822        a
 4823    "});
 4824    cx.update_editor(|e, window, cx| {
 4825        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4826    });
 4827    cx.assert_editor_state(indoc! {"
 4828        «ddddˇ»
 4829        ccc
 4830        bb
 4831        a
 4832    "});
 4833
 4834    // Basic manipulate case
 4835    // Start selection moves to column 0
 4836    // End of selection shrinks to fit shorter line
 4837    cx.set_state(indoc! {"
 4838        dd«d
 4839        ccc
 4840        bb
 4841        aaaaaˇ»
 4842    "});
 4843    cx.update_editor(|e, window, cx| {
 4844        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4845    });
 4846    cx.assert_editor_state(indoc! {"
 4847        «aaaaa
 4848        bb
 4849        ccc
 4850        dddˇ»
 4851    "});
 4852
 4853    // Manipulate case with newlines
 4854    cx.set_state(indoc! {"
 4855        dd«d
 4856        ccc
 4857
 4858        bb
 4859        aaaaa
 4860
 4861        ˇ»
 4862    "});
 4863    cx.update_editor(|e, window, cx| {
 4864        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4865    });
 4866    cx.assert_editor_state(indoc! {"
 4867        «
 4868
 4869        aaaaa
 4870        bb
 4871        ccc
 4872        dddˇ»
 4873
 4874    "});
 4875
 4876    // Adding new line
 4877    cx.set_state(indoc! {"
 4878        aa«a
 4879        bbˇ»b
 4880    "});
 4881    cx.update_editor(|e, window, cx| {
 4882        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4883    });
 4884    cx.assert_editor_state(indoc! {"
 4885        «aaa
 4886        bbb
 4887        added_lineˇ»
 4888    "});
 4889
 4890    // Removing line
 4891    cx.set_state(indoc! {"
 4892        aa«a
 4893        bbbˇ»
 4894    "});
 4895    cx.update_editor(|e, window, cx| {
 4896        e.manipulate_immutable_lines(window, cx, |lines| {
 4897            lines.pop();
 4898        })
 4899    });
 4900    cx.assert_editor_state(indoc! {"
 4901        «aaaˇ»
 4902    "});
 4903
 4904    // Removing all lines
 4905    cx.set_state(indoc! {"
 4906        aa«a
 4907        bbbˇ»
 4908    "});
 4909    cx.update_editor(|e, window, cx| {
 4910        e.manipulate_immutable_lines(window, cx, |lines| {
 4911            lines.drain(..);
 4912        })
 4913    });
 4914    cx.assert_editor_state(indoc! {"
 4915        ˇ
 4916    "});
 4917}
 4918
 4919#[gpui::test]
 4920async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4921    init_test(cx, |_| {});
 4922
 4923    let mut cx = EditorTestContext::new(cx).await;
 4924
 4925    // Consider continuous selection as single selection
 4926    cx.set_state(indoc! {"
 4927        Aaa«aa
 4928        cˇ»c«c
 4929        bb
 4930        aaaˇ»aa
 4931    "});
 4932    cx.update_editor(|e, window, cx| {
 4933        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4934    });
 4935    cx.assert_editor_state(indoc! {"
 4936        «Aaaaa
 4937        ccc
 4938        bb
 4939        aaaaaˇ»
 4940    "});
 4941
 4942    cx.set_state(indoc! {"
 4943        Aaa«aa
 4944        cˇ»c«c
 4945        bb
 4946        aaaˇ»aa
 4947    "});
 4948    cx.update_editor(|e, window, cx| {
 4949        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4950    });
 4951    cx.assert_editor_state(indoc! {"
 4952        «Aaaaa
 4953        ccc
 4954        bbˇ»
 4955    "});
 4956
 4957    // Consider non continuous selection as distinct dedup operations
 4958    cx.set_state(indoc! {"
 4959        «aaaaa
 4960        bb
 4961        aaaaa
 4962        aaaaaˇ»
 4963
 4964        aaa«aaˇ»
 4965    "});
 4966    cx.update_editor(|e, window, cx| {
 4967        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4968    });
 4969    cx.assert_editor_state(indoc! {"
 4970        «aaaaa
 4971        bbˇ»
 4972
 4973        «aaaaaˇ»
 4974    "});
 4975}
 4976
 4977#[gpui::test]
 4978async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4979    init_test(cx, |_| {});
 4980
 4981    let mut cx = EditorTestContext::new(cx).await;
 4982
 4983    cx.set_state(indoc! {"
 4984        «Aaa
 4985        aAa
 4986        Aaaˇ»
 4987    "});
 4988    cx.update_editor(|e, window, cx| {
 4989        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4990    });
 4991    cx.assert_editor_state(indoc! {"
 4992        «Aaa
 4993        aAaˇ»
 4994    "});
 4995
 4996    cx.set_state(indoc! {"
 4997        «Aaa
 4998        aAa
 4999        aaAˇ»
 5000    "});
 5001    cx.update_editor(|e, window, cx| {
 5002        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5003    });
 5004    cx.assert_editor_state(indoc! {"
 5005        «Aaaˇ»
 5006    "});
 5007}
 5008
 5009#[gpui::test]
 5010async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5011    init_test(cx, |_| {});
 5012
 5013    let mut cx = EditorTestContext::new(cx).await;
 5014
 5015    let js_language = Arc::new(Language::new(
 5016        LanguageConfig {
 5017            name: "JavaScript".into(),
 5018            wrap_characters: Some(language::WrapCharactersConfig {
 5019                start_prefix: "<".into(),
 5020                start_suffix: ">".into(),
 5021                end_prefix: "</".into(),
 5022                end_suffix: ">".into(),
 5023            }),
 5024            ..LanguageConfig::default()
 5025        },
 5026        None,
 5027    ));
 5028
 5029    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5030
 5031    cx.set_state(indoc! {"
 5032        «testˇ»
 5033    "});
 5034    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5035    cx.assert_editor_state(indoc! {"
 5036        <«ˇ»>test</«ˇ»>
 5037    "});
 5038
 5039    cx.set_state(indoc! {"
 5040        «test
 5041         testˇ»
 5042    "});
 5043    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5044    cx.assert_editor_state(indoc! {"
 5045        <«ˇ»>test
 5046         test</«ˇ»>
 5047    "});
 5048
 5049    cx.set_state(indoc! {"
 5050        teˇst
 5051    "});
 5052    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5053    cx.assert_editor_state(indoc! {"
 5054        te<«ˇ»></«ˇ»>st
 5055    "});
 5056}
 5057
 5058#[gpui::test]
 5059async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5060    init_test(cx, |_| {});
 5061
 5062    let mut cx = EditorTestContext::new(cx).await;
 5063
 5064    let js_language = Arc::new(Language::new(
 5065        LanguageConfig {
 5066            name: "JavaScript".into(),
 5067            wrap_characters: Some(language::WrapCharactersConfig {
 5068                start_prefix: "<".into(),
 5069                start_suffix: ">".into(),
 5070                end_prefix: "</".into(),
 5071                end_suffix: ">".into(),
 5072            }),
 5073            ..LanguageConfig::default()
 5074        },
 5075        None,
 5076    ));
 5077
 5078    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5079
 5080    cx.set_state(indoc! {"
 5081        «testˇ»
 5082        «testˇ» «testˇ»
 5083        «testˇ»
 5084    "});
 5085    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5086    cx.assert_editor_state(indoc! {"
 5087        <«ˇ»>test</«ˇ»>
 5088        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5089        <«ˇ»>test</«ˇ»>
 5090    "});
 5091
 5092    cx.set_state(indoc! {"
 5093        «test
 5094         testˇ»
 5095        «test
 5096         testˇ»
 5097    "});
 5098    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5099    cx.assert_editor_state(indoc! {"
 5100        <«ˇ»>test
 5101         test</«ˇ»>
 5102        <«ˇ»>test
 5103         test</«ˇ»>
 5104    "});
 5105}
 5106
 5107#[gpui::test]
 5108async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5109    init_test(cx, |_| {});
 5110
 5111    let mut cx = EditorTestContext::new(cx).await;
 5112
 5113    let plaintext_language = Arc::new(Language::new(
 5114        LanguageConfig {
 5115            name: "Plain Text".into(),
 5116            ..LanguageConfig::default()
 5117        },
 5118        None,
 5119    ));
 5120
 5121    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5122
 5123    cx.set_state(indoc! {"
 5124        «testˇ»
 5125    "});
 5126    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5127    cx.assert_editor_state(indoc! {"
 5128      «testˇ»
 5129    "});
 5130}
 5131
 5132#[gpui::test]
 5133async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5134    init_test(cx, |_| {});
 5135
 5136    let mut cx = EditorTestContext::new(cx).await;
 5137
 5138    // Manipulate with multiple selections on a single line
 5139    cx.set_state(indoc! {"
 5140        dd«dd
 5141        cˇ»c«c
 5142        bb
 5143        aaaˇ»aa
 5144    "});
 5145    cx.update_editor(|e, window, cx| {
 5146        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5147    });
 5148    cx.assert_editor_state(indoc! {"
 5149        «aaaaa
 5150        bb
 5151        ccc
 5152        ddddˇ»
 5153    "});
 5154
 5155    // Manipulate with multiple disjoin selections
 5156    cx.set_state(indoc! {"
 5157 5158        4
 5159        3
 5160        2
 5161        1ˇ»
 5162
 5163        dd«dd
 5164        ccc
 5165        bb
 5166        aaaˇ»aa
 5167    "});
 5168    cx.update_editor(|e, window, cx| {
 5169        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5170    });
 5171    cx.assert_editor_state(indoc! {"
 5172        «1
 5173        2
 5174        3
 5175        4
 5176        5ˇ»
 5177
 5178        «aaaaa
 5179        bb
 5180        ccc
 5181        ddddˇ»
 5182    "});
 5183
 5184    // Adding lines on each selection
 5185    cx.set_state(indoc! {"
 5186 5187        1ˇ»
 5188
 5189        bb«bb
 5190        aaaˇ»aa
 5191    "});
 5192    cx.update_editor(|e, window, cx| {
 5193        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5194    });
 5195    cx.assert_editor_state(indoc! {"
 5196        «2
 5197        1
 5198        added lineˇ»
 5199
 5200        «bbbb
 5201        aaaaa
 5202        added lineˇ»
 5203    "});
 5204
 5205    // Removing lines on each selection
 5206    cx.set_state(indoc! {"
 5207 5208        1ˇ»
 5209
 5210        bb«bb
 5211        aaaˇ»aa
 5212    "});
 5213    cx.update_editor(|e, window, cx| {
 5214        e.manipulate_immutable_lines(window, cx, |lines| {
 5215            lines.pop();
 5216        })
 5217    });
 5218    cx.assert_editor_state(indoc! {"
 5219        «2ˇ»
 5220
 5221        «bbbbˇ»
 5222    "});
 5223}
 5224
 5225#[gpui::test]
 5226async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5227    init_test(cx, |settings| {
 5228        settings.defaults.tab_size = NonZeroU32::new(3)
 5229    });
 5230
 5231    let mut cx = EditorTestContext::new(cx).await;
 5232
 5233    // MULTI SELECTION
 5234    // Ln.1 "«" tests empty lines
 5235    // Ln.9 tests just leading whitespace
 5236    cx.set_state(indoc! {"
 5237        «
 5238        abc                 // No indentationˇ»
 5239        «\tabc              // 1 tabˇ»
 5240        \t\tabc «      ˇ»   // 2 tabs
 5241        \t ab«c             // Tab followed by space
 5242         \tabc              // Space followed by tab (3 spaces should be the result)
 5243        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5244           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5245        \t
 5246        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5247    "});
 5248    cx.update_editor(|e, window, cx| {
 5249        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5250    });
 5251    cx.assert_editor_state(
 5252        indoc! {"
 5253            «
 5254            abc                 // No indentation
 5255               abc              // 1 tab
 5256                  abc          // 2 tabs
 5257                abc             // Tab followed by space
 5258               abc              // Space followed by tab (3 spaces should be the result)
 5259                           abc   // Mixed indentation (tab conversion depends on the column)
 5260               abc         // Already space indented
 5261               ·
 5262               abc\tdef          // Only the leading tab is manipulatedˇ»
 5263        "}
 5264        .replace("·", "")
 5265        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5266    );
 5267
 5268    // Test on just a few lines, the others should remain unchanged
 5269    // Only lines (3, 5, 10, 11) should change
 5270    cx.set_state(
 5271        indoc! {"
 5272            ·
 5273            abc                 // No indentation
 5274            \tabcˇ               // 1 tab
 5275            \t\tabc             // 2 tabs
 5276            \t abcˇ              // Tab followed by space
 5277             \tabc              // Space followed by tab (3 spaces should be the result)
 5278            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5279               abc              // Already space indented
 5280            «\t
 5281            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5282        "}
 5283        .replace("·", "")
 5284        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5285    );
 5286    cx.update_editor(|e, window, cx| {
 5287        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5288    });
 5289    cx.assert_editor_state(
 5290        indoc! {"
 5291            ·
 5292            abc                 // No indentation
 5293            «   abc               // 1 tabˇ»
 5294            \t\tabc             // 2 tabs
 5295            «    abc              // Tab followed by spaceˇ»
 5296             \tabc              // Space followed by tab (3 spaces should be the result)
 5297            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5298               abc              // Already space indented
 5299            «   ·
 5300               abc\tdef          // Only the leading tab is manipulatedˇ»
 5301        "}
 5302        .replace("·", "")
 5303        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5304    );
 5305
 5306    // SINGLE SELECTION
 5307    // Ln.1 "«" tests empty lines
 5308    // Ln.9 tests just leading whitespace
 5309    cx.set_state(indoc! {"
 5310        «
 5311        abc                 // No indentation
 5312        \tabc               // 1 tab
 5313        \t\tabc             // 2 tabs
 5314        \t abc              // Tab followed by space
 5315         \tabc              // Space followed by tab (3 spaces should be the result)
 5316        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5317           abc              // Already space indented
 5318        \t
 5319        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5320    "});
 5321    cx.update_editor(|e, window, cx| {
 5322        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5323    });
 5324    cx.assert_editor_state(
 5325        indoc! {"
 5326            «
 5327            abc                 // No indentation
 5328               abc               // 1 tab
 5329                  abc             // 2 tabs
 5330                abc              // Tab followed by space
 5331               abc              // Space followed by tab (3 spaces should be the result)
 5332                           abc   // Mixed indentation (tab conversion depends on the column)
 5333               abc              // Already space indented
 5334               ·
 5335               abc\tdef          // Only the leading tab is manipulatedˇ»
 5336        "}
 5337        .replace("·", "")
 5338        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5339    );
 5340}
 5341
 5342#[gpui::test]
 5343async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5344    init_test(cx, |settings| {
 5345        settings.defaults.tab_size = NonZeroU32::new(3)
 5346    });
 5347
 5348    let mut cx = EditorTestContext::new(cx).await;
 5349
 5350    // MULTI SELECTION
 5351    // Ln.1 "«" tests empty lines
 5352    // Ln.11 tests just leading whitespace
 5353    cx.set_state(indoc! {"
 5354        «
 5355        abˇ»ˇc                 // No indentation
 5356         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5357          abc  «             // 2 spaces (< 3 so dont convert)
 5358           abc              // 3 spaces (convert)
 5359             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5360        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5361        «\t abc              // Tab followed by space
 5362         \tabc              // Space followed by tab (should be consumed due to tab)
 5363        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5364           \tˇ»  «\t
 5365           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5366    "});
 5367    cx.update_editor(|e, window, cx| {
 5368        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5369    });
 5370    cx.assert_editor_state(indoc! {"
 5371        «
 5372        abc                 // No indentation
 5373         abc                // 1 space (< 3 so dont convert)
 5374          abc               // 2 spaces (< 3 so dont convert)
 5375        \tabc              // 3 spaces (convert)
 5376        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5377        \t\t\tabc           // Already tab indented
 5378        \t abc              // Tab followed by space
 5379        \tabc              // Space followed by tab (should be consumed due to tab)
 5380        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5381        \t\t\t
 5382        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5383    "});
 5384
 5385    // Test on just a few lines, the other should remain unchanged
 5386    // Only lines (4, 8, 11, 12) should change
 5387    cx.set_state(
 5388        indoc! {"
 5389            ·
 5390            abc                 // No indentation
 5391             abc                // 1 space (< 3 so dont convert)
 5392              abc               // 2 spaces (< 3 so dont convert)
 5393            «   abc              // 3 spaces (convert)ˇ»
 5394                 abc            // 5 spaces (1 tab + 2 spaces)
 5395            \t\t\tabc           // Already tab indented
 5396            \t abc              // Tab followed by space
 5397             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5398               \t\t  \tabc      // Mixed indentation
 5399            \t \t  \t   \tabc   // Mixed indentation
 5400               \t  \tˇ
 5401            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5402        "}
 5403        .replace("·", "")
 5404        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5405    );
 5406    cx.update_editor(|e, window, cx| {
 5407        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5408    });
 5409    cx.assert_editor_state(
 5410        indoc! {"
 5411            ·
 5412            abc                 // No indentation
 5413             abc                // 1 space (< 3 so dont convert)
 5414              abc               // 2 spaces (< 3 so dont convert)
 5415            «\tabc              // 3 spaces (convert)ˇ»
 5416                 abc            // 5 spaces (1 tab + 2 spaces)
 5417            \t\t\tabc           // Already tab indented
 5418            \t abc              // Tab followed by space
 5419            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5420               \t\t  \tabc      // Mixed indentation
 5421            \t \t  \t   \tabc   // Mixed indentation
 5422            «\t\t\t
 5423            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5424        "}
 5425        .replace("·", "")
 5426        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5427    );
 5428
 5429    // SINGLE SELECTION
 5430    // Ln.1 "«" tests empty lines
 5431    // Ln.11 tests just leading whitespace
 5432    cx.set_state(indoc! {"
 5433        «
 5434        abc                 // No indentation
 5435         abc                // 1 space (< 3 so dont convert)
 5436          abc               // 2 spaces (< 3 so dont convert)
 5437           abc              // 3 spaces (convert)
 5438             abc            // 5 spaces (1 tab + 2 spaces)
 5439        \t\t\tabc           // Already tab indented
 5440        \t abc              // Tab followed by space
 5441         \tabc              // Space followed by tab (should be consumed due to tab)
 5442        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5443           \t  \t
 5444           abc   \t         // Only the leading spaces should be convertedˇ»
 5445    "});
 5446    cx.update_editor(|e, window, cx| {
 5447        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5448    });
 5449    cx.assert_editor_state(indoc! {"
 5450        «
 5451        abc                 // No indentation
 5452         abc                // 1 space (< 3 so dont convert)
 5453          abc               // 2 spaces (< 3 so dont convert)
 5454        \tabc              // 3 spaces (convert)
 5455        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5456        \t\t\tabc           // Already tab indented
 5457        \t abc              // Tab followed by space
 5458        \tabc              // Space followed by tab (should be consumed due to tab)
 5459        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5460        \t\t\t
 5461        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5462    "});
 5463}
 5464
 5465#[gpui::test]
 5466async fn test_toggle_case(cx: &mut TestAppContext) {
 5467    init_test(cx, |_| {});
 5468
 5469    let mut cx = EditorTestContext::new(cx).await;
 5470
 5471    // If all lower case -> upper case
 5472    cx.set_state(indoc! {"
 5473        «hello worldˇ»
 5474    "});
 5475    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5476    cx.assert_editor_state(indoc! {"
 5477        «HELLO WORLDˇ»
 5478    "});
 5479
 5480    // If all upper case -> lower case
 5481    cx.set_state(indoc! {"
 5482        «HELLO WORLDˇ»
 5483    "});
 5484    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5485    cx.assert_editor_state(indoc! {"
 5486        «hello worldˇ»
 5487    "});
 5488
 5489    // If any upper case characters are identified -> lower case
 5490    // This matches JetBrains IDEs
 5491    cx.set_state(indoc! {"
 5492        «hEllo worldˇ»
 5493    "});
 5494    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5495    cx.assert_editor_state(indoc! {"
 5496        «hello worldˇ»
 5497    "});
 5498}
 5499
 5500#[gpui::test]
 5501async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5502    init_test(cx, |_| {});
 5503
 5504    let mut cx = EditorTestContext::new(cx).await;
 5505
 5506    cx.set_state(indoc! {"
 5507        «implement-windows-supportˇ»
 5508    "});
 5509    cx.update_editor(|e, window, cx| {
 5510        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5511    });
 5512    cx.assert_editor_state(indoc! {"
 5513        «Implement windows supportˇ»
 5514    "});
 5515}
 5516
 5517#[gpui::test]
 5518async fn test_manipulate_text(cx: &mut TestAppContext) {
 5519    init_test(cx, |_| {});
 5520
 5521    let mut cx = EditorTestContext::new(cx).await;
 5522
 5523    // Test convert_to_upper_case()
 5524    cx.set_state(indoc! {"
 5525        «hello worldˇ»
 5526    "});
 5527    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5528    cx.assert_editor_state(indoc! {"
 5529        «HELLO WORLDˇ»
 5530    "});
 5531
 5532    // Test convert_to_lower_case()
 5533    cx.set_state(indoc! {"
 5534        «HELLO WORLDˇ»
 5535    "});
 5536    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5537    cx.assert_editor_state(indoc! {"
 5538        «hello worldˇ»
 5539    "});
 5540
 5541    // Test multiple line, single selection case
 5542    cx.set_state(indoc! {"
 5543        «The quick brown
 5544        fox jumps over
 5545        the lazy dogˇ»
 5546    "});
 5547    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5548    cx.assert_editor_state(indoc! {"
 5549        «The Quick Brown
 5550        Fox Jumps Over
 5551        The Lazy Dogˇ»
 5552    "});
 5553
 5554    // Test multiple line, single selection case
 5555    cx.set_state(indoc! {"
 5556        «The quick brown
 5557        fox jumps over
 5558        the lazy dogˇ»
 5559    "});
 5560    cx.update_editor(|e, window, cx| {
 5561        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5562    });
 5563    cx.assert_editor_state(indoc! {"
 5564        «TheQuickBrown
 5565        FoxJumpsOver
 5566        TheLazyDogˇ»
 5567    "});
 5568
 5569    // From here on out, test more complex cases of manipulate_text()
 5570
 5571    // Test no selection case - should affect words cursors are in
 5572    // Cursor at beginning, middle, and end of word
 5573    cx.set_state(indoc! {"
 5574        ˇhello big beauˇtiful worldˇ
 5575    "});
 5576    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5577    cx.assert_editor_state(indoc! {"
 5578        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5579    "});
 5580
 5581    // Test multiple selections on a single line and across multiple lines
 5582    cx.set_state(indoc! {"
 5583        «Theˇ» quick «brown
 5584        foxˇ» jumps «overˇ»
 5585        the «lazyˇ» dog
 5586    "});
 5587    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5588    cx.assert_editor_state(indoc! {"
 5589        «THEˇ» quick «BROWN
 5590        FOXˇ» jumps «OVERˇ»
 5591        the «LAZYˇ» dog
 5592    "});
 5593
 5594    // Test case where text length grows
 5595    cx.set_state(indoc! {"
 5596        «tschüߡ»
 5597    "});
 5598    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5599    cx.assert_editor_state(indoc! {"
 5600        «TSCHÜSSˇ»
 5601    "});
 5602
 5603    // Test to make sure we don't crash when text shrinks
 5604    cx.set_state(indoc! {"
 5605        aaa_bbbˇ
 5606    "});
 5607    cx.update_editor(|e, window, cx| {
 5608        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5609    });
 5610    cx.assert_editor_state(indoc! {"
 5611        «aaaBbbˇ»
 5612    "});
 5613
 5614    // Test to make sure we all aware of the fact that each word can grow and shrink
 5615    // Final selections should be aware of this fact
 5616    cx.set_state(indoc! {"
 5617        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5618    "});
 5619    cx.update_editor(|e, window, cx| {
 5620        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5621    });
 5622    cx.assert_editor_state(indoc! {"
 5623        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5624    "});
 5625
 5626    cx.set_state(indoc! {"
 5627        «hElLo, WoRld!ˇ»
 5628    "});
 5629    cx.update_editor(|e, window, cx| {
 5630        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5631    });
 5632    cx.assert_editor_state(indoc! {"
 5633        «HeLlO, wOrLD!ˇ»
 5634    "});
 5635
 5636    // Test selections with `line_mode() = true`.
 5637    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5638    cx.set_state(indoc! {"
 5639        «The quick brown
 5640        fox jumps over
 5641        tˇ»he lazy dog
 5642    "});
 5643    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5644    cx.assert_editor_state(indoc! {"
 5645        «THE QUICK BROWN
 5646        FOX JUMPS OVER
 5647        THE LAZY DOGˇ»
 5648    "});
 5649}
 5650
 5651#[gpui::test]
 5652fn test_duplicate_line(cx: &mut TestAppContext) {
 5653    init_test(cx, |_| {});
 5654
 5655    let editor = cx.add_window(|window, cx| {
 5656        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5657        build_editor(buffer, window, cx)
 5658    });
 5659    _ = editor.update(cx, |editor, window, cx| {
 5660        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5661            s.select_display_ranges([
 5662                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5663                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5664                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5665                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5666            ])
 5667        });
 5668        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5669        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5670        assert_eq!(
 5671            display_ranges(editor, cx),
 5672            vec![
 5673                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5674                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5675                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5676                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5677            ]
 5678        );
 5679    });
 5680
 5681    let editor = cx.add_window(|window, cx| {
 5682        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5683        build_editor(buffer, window, cx)
 5684    });
 5685    _ = editor.update(cx, |editor, window, cx| {
 5686        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5687            s.select_display_ranges([
 5688                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5689                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5690            ])
 5691        });
 5692        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5693        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5694        assert_eq!(
 5695            display_ranges(editor, cx),
 5696            vec![
 5697                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5698                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5699            ]
 5700        );
 5701    });
 5702
 5703    // With `duplicate_line_up` the selections move to the duplicated lines,
 5704    // which are inserted above the original lines
 5705    let editor = cx.add_window(|window, cx| {
 5706        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5707        build_editor(buffer, window, cx)
 5708    });
 5709    _ = editor.update(cx, |editor, window, cx| {
 5710        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5711            s.select_display_ranges([
 5712                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5713                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5714                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5715                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5716            ])
 5717        });
 5718        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5719        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5720        assert_eq!(
 5721            display_ranges(editor, cx),
 5722            vec![
 5723                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5724                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5725                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5726                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5727            ]
 5728        );
 5729    });
 5730
 5731    let editor = cx.add_window(|window, cx| {
 5732        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5733        build_editor(buffer, window, cx)
 5734    });
 5735    _ = editor.update(cx, |editor, window, cx| {
 5736        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5737            s.select_display_ranges([
 5738                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5739                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5740            ])
 5741        });
 5742        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5743        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5744        assert_eq!(
 5745            display_ranges(editor, cx),
 5746            vec![
 5747                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5748                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5749            ]
 5750        );
 5751    });
 5752
 5753    let editor = cx.add_window(|window, cx| {
 5754        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5755        build_editor(buffer, window, cx)
 5756    });
 5757    _ = editor.update(cx, |editor, window, cx| {
 5758        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5759            s.select_display_ranges([
 5760                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5761                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5762            ])
 5763        });
 5764        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5765        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5766        assert_eq!(
 5767            display_ranges(editor, cx),
 5768            vec![
 5769                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5770                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5771            ]
 5772        );
 5773    });
 5774}
 5775
 5776#[gpui::test]
 5777async fn test_rotate_selections(cx: &mut TestAppContext) {
 5778    init_test(cx, |_| {});
 5779
 5780    let mut cx = EditorTestContext::new(cx).await;
 5781
 5782    // Rotate text selections (horizontal)
 5783    cx.set_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5784    cx.update_editor(|e, window, cx| {
 5785        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5786    });
 5787    cx.assert_editor_state("x=«3ˇ», y=«1ˇ», z=«2ˇ»");
 5788    cx.update_editor(|e, window, cx| {
 5789        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5790    });
 5791    cx.assert_editor_state("x=«1ˇ», y=«2ˇ», z=«3ˇ»");
 5792
 5793    // Rotate text selections (vertical)
 5794    cx.set_state(indoc! {"
 5795        x=«1ˇ»
 5796        y=«2ˇ»
 5797        z=«3ˇ»
 5798    "});
 5799    cx.update_editor(|e, window, cx| {
 5800        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5801    });
 5802    cx.assert_editor_state(indoc! {"
 5803        x=«3ˇ»
 5804        y=«1ˇ»
 5805        z=«2ˇ»
 5806    "});
 5807    cx.update_editor(|e, window, cx| {
 5808        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5809    });
 5810    cx.assert_editor_state(indoc! {"
 5811        x=«1ˇ»
 5812        y=«2ˇ»
 5813        z=«3ˇ»
 5814    "});
 5815
 5816    // Rotate text selections (vertical, different lengths)
 5817    cx.set_state(indoc! {"
 5818        x=\"«ˇ»\"
 5819        y=\"«aˇ»\"
 5820        z=\"«aaˇ»\"
 5821    "});
 5822    cx.update_editor(|e, window, cx| {
 5823        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5824    });
 5825    cx.assert_editor_state(indoc! {"
 5826        x=\"«aaˇ»\"
 5827        y=\"«ˇ»\"
 5828        z=\"«aˇ»\"
 5829    "});
 5830    cx.update_editor(|e, window, cx| {
 5831        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5832    });
 5833    cx.assert_editor_state(indoc! {"
 5834        x=\"«ˇ»\"
 5835        y=\"«aˇ»\"
 5836        z=\"«aaˇ»\"
 5837    "});
 5838
 5839    // Rotate whole lines (cursor positions preserved)
 5840    cx.set_state(indoc! {"
 5841        ˇline123
 5842        liˇne23
 5843        line3ˇ
 5844    "});
 5845    cx.update_editor(|e, window, cx| {
 5846        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5847    });
 5848    cx.assert_editor_state(indoc! {"
 5849        line3ˇ
 5850        ˇline123
 5851        liˇne23
 5852    "});
 5853    cx.update_editor(|e, window, cx| {
 5854        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5855    });
 5856    cx.assert_editor_state(indoc! {"
 5857        ˇline123
 5858        liˇne23
 5859        line3ˇ
 5860    "});
 5861
 5862    // Rotate whole lines, multiple cursors per line (positions preserved)
 5863    cx.set_state(indoc! {"
 5864        ˇliˇne123
 5865        ˇline23
 5866        ˇline3
 5867    "});
 5868    cx.update_editor(|e, window, cx| {
 5869        e.rotate_selections_forward(&RotateSelectionsForward, window, cx)
 5870    });
 5871    cx.assert_editor_state(indoc! {"
 5872        ˇline3
 5873        ˇliˇne123
 5874        ˇline23
 5875    "});
 5876    cx.update_editor(|e, window, cx| {
 5877        e.rotate_selections_backward(&RotateSelectionsBackward, window, cx)
 5878    });
 5879    cx.assert_editor_state(indoc! {"
 5880        ˇliˇne123
 5881        ˇline23
 5882        ˇline3
 5883    "});
 5884}
 5885
 5886#[gpui::test]
 5887fn test_move_line_up_down(cx: &mut TestAppContext) {
 5888    init_test(cx, |_| {});
 5889
 5890    let editor = cx.add_window(|window, cx| {
 5891        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5892        build_editor(buffer, window, cx)
 5893    });
 5894    _ = editor.update(cx, |editor, window, cx| {
 5895        editor.fold_creases(
 5896            vec![
 5897                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5898                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5899                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5900            ],
 5901            true,
 5902            window,
 5903            cx,
 5904        );
 5905        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5906            s.select_display_ranges([
 5907                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5908                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5909                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5910                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5911            ])
 5912        });
 5913        assert_eq!(
 5914            editor.display_text(cx),
 5915            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5916        );
 5917
 5918        editor.move_line_up(&MoveLineUp, window, cx);
 5919        assert_eq!(
 5920            editor.display_text(cx),
 5921            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5922        );
 5923        assert_eq!(
 5924            display_ranges(editor, cx),
 5925            vec![
 5926                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5927                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5928                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5929                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5930            ]
 5931        );
 5932    });
 5933
 5934    _ = editor.update(cx, |editor, window, cx| {
 5935        editor.move_line_down(&MoveLineDown, window, cx);
 5936        assert_eq!(
 5937            editor.display_text(cx),
 5938            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5939        );
 5940        assert_eq!(
 5941            display_ranges(editor, cx),
 5942            vec![
 5943                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5944                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5945                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5946                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5947            ]
 5948        );
 5949    });
 5950
 5951    _ = editor.update(cx, |editor, window, cx| {
 5952        editor.move_line_down(&MoveLineDown, window, cx);
 5953        assert_eq!(
 5954            editor.display_text(cx),
 5955            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5956        );
 5957        assert_eq!(
 5958            display_ranges(editor, cx),
 5959            vec![
 5960                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5961                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5962                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5963                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5964            ]
 5965        );
 5966    });
 5967
 5968    _ = editor.update(cx, |editor, window, cx| {
 5969        editor.move_line_up(&MoveLineUp, window, cx);
 5970        assert_eq!(
 5971            editor.display_text(cx),
 5972            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5973        );
 5974        assert_eq!(
 5975            display_ranges(editor, cx),
 5976            vec![
 5977                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5978                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5979                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5980                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5981            ]
 5982        );
 5983    });
 5984}
 5985
 5986#[gpui::test]
 5987fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5988    init_test(cx, |_| {});
 5989    let editor = cx.add_window(|window, cx| {
 5990        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5991        build_editor(buffer, window, cx)
 5992    });
 5993    _ = editor.update(cx, |editor, window, cx| {
 5994        editor.fold_creases(
 5995            vec![Crease::simple(
 5996                Point::new(6, 4)..Point::new(7, 4),
 5997                FoldPlaceholder::test(),
 5998            )],
 5999            true,
 6000            window,
 6001            cx,
 6002        );
 6003        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6004            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 6005        });
 6006        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 6007        editor.move_line_up(&MoveLineUp, window, cx);
 6008        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 6009        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 6010    });
 6011}
 6012
 6013#[gpui::test]
 6014fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 6015    init_test(cx, |_| {});
 6016
 6017    let editor = cx.add_window(|window, cx| {
 6018        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 6019        build_editor(buffer, window, cx)
 6020    });
 6021    _ = editor.update(cx, |editor, window, cx| {
 6022        let snapshot = editor.buffer.read(cx).snapshot(cx);
 6023        editor.insert_blocks(
 6024            [BlockProperties {
 6025                style: BlockStyle::Fixed,
 6026                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 6027                height: Some(1),
 6028                render: Arc::new(|_| div().into_any()),
 6029                priority: 0,
 6030            }],
 6031            Some(Autoscroll::fit()),
 6032            cx,
 6033        );
 6034        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6035            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 6036        });
 6037        editor.move_line_down(&MoveLineDown, window, cx);
 6038    });
 6039}
 6040
 6041#[gpui::test]
 6042async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 6043    init_test(cx, |_| {});
 6044
 6045    let mut cx = EditorTestContext::new(cx).await;
 6046    cx.set_state(
 6047        &"
 6048            ˇzero
 6049            one
 6050            two
 6051            three
 6052            four
 6053            five
 6054        "
 6055        .unindent(),
 6056    );
 6057
 6058    // Create a four-line block that replaces three lines of text.
 6059    cx.update_editor(|editor, window, cx| {
 6060        let snapshot = editor.snapshot(window, cx);
 6061        let snapshot = &snapshot.buffer_snapshot();
 6062        let placement = BlockPlacement::Replace(
 6063            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 6064        );
 6065        editor.insert_blocks(
 6066            [BlockProperties {
 6067                placement,
 6068                height: Some(4),
 6069                style: BlockStyle::Sticky,
 6070                render: Arc::new(|_| gpui::div().into_any_element()),
 6071                priority: 0,
 6072            }],
 6073            None,
 6074            cx,
 6075        );
 6076    });
 6077
 6078    // Move down so that the cursor touches the block.
 6079    cx.update_editor(|editor, window, cx| {
 6080        editor.move_down(&Default::default(), window, cx);
 6081    });
 6082    cx.assert_editor_state(
 6083        &"
 6084            zero
 6085            «one
 6086            two
 6087            threeˇ»
 6088            four
 6089            five
 6090        "
 6091        .unindent(),
 6092    );
 6093
 6094    // Move down past the block.
 6095    cx.update_editor(|editor, window, cx| {
 6096        editor.move_down(&Default::default(), window, cx);
 6097    });
 6098    cx.assert_editor_state(
 6099        &"
 6100            zero
 6101            one
 6102            two
 6103            three
 6104            ˇfour
 6105            five
 6106        "
 6107        .unindent(),
 6108    );
 6109}
 6110
 6111#[gpui::test]
 6112fn test_transpose(cx: &mut TestAppContext) {
 6113    init_test(cx, |_| {});
 6114
 6115    _ = cx.add_window(|window, cx| {
 6116        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6117        editor.set_style(EditorStyle::default(), window, cx);
 6118        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6119            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6120        });
 6121        editor.transpose(&Default::default(), window, cx);
 6122        assert_eq!(editor.text(cx), "bac");
 6123        assert_eq!(
 6124            editor.selections.ranges(&editor.display_snapshot(cx)),
 6125            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6126        );
 6127
 6128        editor.transpose(&Default::default(), window, cx);
 6129        assert_eq!(editor.text(cx), "bca");
 6130        assert_eq!(
 6131            editor.selections.ranges(&editor.display_snapshot(cx)),
 6132            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6133        );
 6134
 6135        editor.transpose(&Default::default(), window, cx);
 6136        assert_eq!(editor.text(cx), "bac");
 6137        assert_eq!(
 6138            editor.selections.ranges(&editor.display_snapshot(cx)),
 6139            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6140        );
 6141
 6142        editor
 6143    });
 6144
 6145    _ = cx.add_window(|window, cx| {
 6146        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6147        editor.set_style(EditorStyle::default(), window, cx);
 6148        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6149            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6150        });
 6151        editor.transpose(&Default::default(), window, cx);
 6152        assert_eq!(editor.text(cx), "acb\nde");
 6153        assert_eq!(
 6154            editor.selections.ranges(&editor.display_snapshot(cx)),
 6155            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6156        );
 6157
 6158        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6159            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6160        });
 6161        editor.transpose(&Default::default(), window, cx);
 6162        assert_eq!(editor.text(cx), "acbd\ne");
 6163        assert_eq!(
 6164            editor.selections.ranges(&editor.display_snapshot(cx)),
 6165            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6166        );
 6167
 6168        editor.transpose(&Default::default(), window, cx);
 6169        assert_eq!(editor.text(cx), "acbde\n");
 6170        assert_eq!(
 6171            editor.selections.ranges(&editor.display_snapshot(cx)),
 6172            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6173        );
 6174
 6175        editor.transpose(&Default::default(), window, cx);
 6176        assert_eq!(editor.text(cx), "acbd\ne");
 6177        assert_eq!(
 6178            editor.selections.ranges(&editor.display_snapshot(cx)),
 6179            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6180        );
 6181
 6182        editor
 6183    });
 6184
 6185    _ = cx.add_window(|window, cx| {
 6186        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6187        editor.set_style(EditorStyle::default(), window, cx);
 6188        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6189            s.select_ranges([
 6190                MultiBufferOffset(1)..MultiBufferOffset(1),
 6191                MultiBufferOffset(2)..MultiBufferOffset(2),
 6192                MultiBufferOffset(4)..MultiBufferOffset(4),
 6193            ])
 6194        });
 6195        editor.transpose(&Default::default(), window, cx);
 6196        assert_eq!(editor.text(cx), "bacd\ne");
 6197        assert_eq!(
 6198            editor.selections.ranges(&editor.display_snapshot(cx)),
 6199            [
 6200                MultiBufferOffset(2)..MultiBufferOffset(2),
 6201                MultiBufferOffset(3)..MultiBufferOffset(3),
 6202                MultiBufferOffset(5)..MultiBufferOffset(5)
 6203            ]
 6204        );
 6205
 6206        editor.transpose(&Default::default(), window, cx);
 6207        assert_eq!(editor.text(cx), "bcade\n");
 6208        assert_eq!(
 6209            editor.selections.ranges(&editor.display_snapshot(cx)),
 6210            [
 6211                MultiBufferOffset(3)..MultiBufferOffset(3),
 6212                MultiBufferOffset(4)..MultiBufferOffset(4),
 6213                MultiBufferOffset(6)..MultiBufferOffset(6)
 6214            ]
 6215        );
 6216
 6217        editor.transpose(&Default::default(), window, cx);
 6218        assert_eq!(editor.text(cx), "bcda\ne");
 6219        assert_eq!(
 6220            editor.selections.ranges(&editor.display_snapshot(cx)),
 6221            [
 6222                MultiBufferOffset(4)..MultiBufferOffset(4),
 6223                MultiBufferOffset(6)..MultiBufferOffset(6)
 6224            ]
 6225        );
 6226
 6227        editor.transpose(&Default::default(), window, cx);
 6228        assert_eq!(editor.text(cx), "bcade\n");
 6229        assert_eq!(
 6230            editor.selections.ranges(&editor.display_snapshot(cx)),
 6231            [
 6232                MultiBufferOffset(4)..MultiBufferOffset(4),
 6233                MultiBufferOffset(6)..MultiBufferOffset(6)
 6234            ]
 6235        );
 6236
 6237        editor.transpose(&Default::default(), window, cx);
 6238        assert_eq!(editor.text(cx), "bcaed\n");
 6239        assert_eq!(
 6240            editor.selections.ranges(&editor.display_snapshot(cx)),
 6241            [
 6242                MultiBufferOffset(5)..MultiBufferOffset(5),
 6243                MultiBufferOffset(6)..MultiBufferOffset(6)
 6244            ]
 6245        );
 6246
 6247        editor
 6248    });
 6249
 6250    _ = cx.add_window(|window, cx| {
 6251        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6252        editor.set_style(EditorStyle::default(), window, cx);
 6253        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6254            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6255        });
 6256        editor.transpose(&Default::default(), window, cx);
 6257        assert_eq!(editor.text(cx), "🏀🍐✋");
 6258        assert_eq!(
 6259            editor.selections.ranges(&editor.display_snapshot(cx)),
 6260            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6261        );
 6262
 6263        editor.transpose(&Default::default(), window, cx);
 6264        assert_eq!(editor.text(cx), "🏀✋🍐");
 6265        assert_eq!(
 6266            editor.selections.ranges(&editor.display_snapshot(cx)),
 6267            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6268        );
 6269
 6270        editor.transpose(&Default::default(), window, cx);
 6271        assert_eq!(editor.text(cx), "🏀🍐✋");
 6272        assert_eq!(
 6273            editor.selections.ranges(&editor.display_snapshot(cx)),
 6274            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6275        );
 6276
 6277        editor
 6278    });
 6279}
 6280
 6281#[gpui::test]
 6282async fn test_rewrap(cx: &mut TestAppContext) {
 6283    init_test(cx, |settings| {
 6284        settings.languages.0.extend([
 6285            (
 6286                "Markdown".into(),
 6287                LanguageSettingsContent {
 6288                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6289                    preferred_line_length: Some(40),
 6290                    ..Default::default()
 6291                },
 6292            ),
 6293            (
 6294                "Plain Text".into(),
 6295                LanguageSettingsContent {
 6296                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6297                    preferred_line_length: Some(40),
 6298                    ..Default::default()
 6299                },
 6300            ),
 6301            (
 6302                "C++".into(),
 6303                LanguageSettingsContent {
 6304                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6305                    preferred_line_length: Some(40),
 6306                    ..Default::default()
 6307                },
 6308            ),
 6309            (
 6310                "Python".into(),
 6311                LanguageSettingsContent {
 6312                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6313                    preferred_line_length: Some(40),
 6314                    ..Default::default()
 6315                },
 6316            ),
 6317            (
 6318                "Rust".into(),
 6319                LanguageSettingsContent {
 6320                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6321                    preferred_line_length: Some(40),
 6322                    ..Default::default()
 6323                },
 6324            ),
 6325        ])
 6326    });
 6327
 6328    let mut cx = EditorTestContext::new(cx).await;
 6329
 6330    let cpp_language = Arc::new(Language::new(
 6331        LanguageConfig {
 6332            name: "C++".into(),
 6333            line_comments: vec!["// ".into()],
 6334            ..LanguageConfig::default()
 6335        },
 6336        None,
 6337    ));
 6338    let python_language = Arc::new(Language::new(
 6339        LanguageConfig {
 6340            name: "Python".into(),
 6341            line_comments: vec!["# ".into()],
 6342            ..LanguageConfig::default()
 6343        },
 6344        None,
 6345    ));
 6346    let markdown_language = Arc::new(Language::new(
 6347        LanguageConfig {
 6348            name: "Markdown".into(),
 6349            rewrap_prefixes: vec![
 6350                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6351                regex::Regex::new("[-*+]\\s+").unwrap(),
 6352            ],
 6353            ..LanguageConfig::default()
 6354        },
 6355        None,
 6356    ));
 6357    let rust_language = Arc::new(
 6358        Language::new(
 6359            LanguageConfig {
 6360                name: "Rust".into(),
 6361                line_comments: vec!["// ".into(), "/// ".into()],
 6362                ..LanguageConfig::default()
 6363            },
 6364            Some(tree_sitter_rust::LANGUAGE.into()),
 6365        )
 6366        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6367        .unwrap(),
 6368    );
 6369
 6370    let plaintext_language = Arc::new(Language::new(
 6371        LanguageConfig {
 6372            name: "Plain Text".into(),
 6373            ..LanguageConfig::default()
 6374        },
 6375        None,
 6376    ));
 6377
 6378    // Test basic rewrapping of a long line with a cursor
 6379    assert_rewrap(
 6380        indoc! {"
 6381            // ˇThis is a long comment that needs to be wrapped.
 6382        "},
 6383        indoc! {"
 6384            // ˇThis is a long comment that needs to
 6385            // be wrapped.
 6386        "},
 6387        cpp_language.clone(),
 6388        &mut cx,
 6389    );
 6390
 6391    // Test rewrapping a full selection
 6392    assert_rewrap(
 6393        indoc! {"
 6394            «// This selected long comment needs to be wrapped.ˇ»"
 6395        },
 6396        indoc! {"
 6397            «// This selected long comment needs to
 6398            // be wrapped.ˇ»"
 6399        },
 6400        cpp_language.clone(),
 6401        &mut cx,
 6402    );
 6403
 6404    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6405    assert_rewrap(
 6406        indoc! {"
 6407            // ˇThis is the first line.
 6408            // Thisˇ is the second line.
 6409            // This is the thirdˇ line, all part of one paragraph.
 6410         "},
 6411        indoc! {"
 6412            // ˇThis is the first line. Thisˇ is the
 6413            // second line. This is the thirdˇ line,
 6414            // all part of one paragraph.
 6415         "},
 6416        cpp_language.clone(),
 6417        &mut cx,
 6418    );
 6419
 6420    // Test multiple cursors in different paragraphs trigger separate rewraps
 6421    assert_rewrap(
 6422        indoc! {"
 6423            // ˇThis is the first paragraph, first line.
 6424            // ˇThis is the first paragraph, second line.
 6425
 6426            // ˇThis is the second paragraph, first line.
 6427            // ˇThis is the second paragraph, second line.
 6428        "},
 6429        indoc! {"
 6430            // ˇThis is the first paragraph, first
 6431            // line. ˇThis is the first paragraph,
 6432            // second line.
 6433
 6434            // ˇThis is the second paragraph, first
 6435            // line. ˇThis is the second paragraph,
 6436            // second line.
 6437        "},
 6438        cpp_language.clone(),
 6439        &mut cx,
 6440    );
 6441
 6442    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6443    assert_rewrap(
 6444        indoc! {"
 6445            «// A regular long long comment to be wrapped.
 6446            /// A documentation long comment to be wrapped.ˇ»
 6447          "},
 6448        indoc! {"
 6449            «// A regular long long comment to be
 6450            // wrapped.
 6451            /// A documentation long comment to be
 6452            /// wrapped.ˇ»
 6453          "},
 6454        rust_language.clone(),
 6455        &mut cx,
 6456    );
 6457
 6458    // Test that change in indentation level trigger seperate rewraps
 6459    assert_rewrap(
 6460        indoc! {"
 6461            fn foo() {
 6462                «// This is a long comment at the base indent.
 6463                    // This is a long comment at the next indent.ˇ»
 6464            }
 6465        "},
 6466        indoc! {"
 6467            fn foo() {
 6468                «// This is a long comment at the
 6469                // base indent.
 6470                    // This is a long comment at the
 6471                    // next indent.ˇ»
 6472            }
 6473        "},
 6474        rust_language.clone(),
 6475        &mut cx,
 6476    );
 6477
 6478    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6479    assert_rewrap(
 6480        indoc! {"
 6481            # ˇThis is a long comment using a pound sign.
 6482        "},
 6483        indoc! {"
 6484            # ˇThis is a long comment using a pound
 6485            # sign.
 6486        "},
 6487        python_language,
 6488        &mut cx,
 6489    );
 6490
 6491    // Test rewrapping only affects comments, not code even when selected
 6492    assert_rewrap(
 6493        indoc! {"
 6494            «/// This doc comment is long and should be wrapped.
 6495            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6496        "},
 6497        indoc! {"
 6498            «/// This doc comment is long and should
 6499            /// be wrapped.
 6500            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6501        "},
 6502        rust_language.clone(),
 6503        &mut cx,
 6504    );
 6505
 6506    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6507    assert_rewrap(
 6508        indoc! {"
 6509            # Header
 6510
 6511            A long long long line of markdown text to wrap.ˇ
 6512         "},
 6513        indoc! {"
 6514            # Header
 6515
 6516            A long long long line of markdown text
 6517            to wrap.ˇ
 6518         "},
 6519        markdown_language.clone(),
 6520        &mut cx,
 6521    );
 6522
 6523    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6524    assert_rewrap(
 6525        indoc! {"
 6526            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6527            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6528            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6529        "},
 6530        indoc! {"
 6531            «1. This is a numbered list item that is
 6532               very long and needs to be wrapped
 6533               properly.
 6534            2. This is a numbered list item that is
 6535               very long and needs to be wrapped
 6536               properly.
 6537            - This is an unordered list item that is
 6538              also very long and should not merge
 6539              with the numbered item.ˇ»
 6540        "},
 6541        markdown_language.clone(),
 6542        &mut cx,
 6543    );
 6544
 6545    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6546    assert_rewrap(
 6547        indoc! {"
 6548            «1. This is a numbered list item that is
 6549            very long and needs to be wrapped
 6550            properly.
 6551            2. This is a numbered list item that is
 6552            very long and needs to be wrapped
 6553            properly.
 6554            - This is an unordered list item that is
 6555            also very long and should not merge with
 6556            the numbered item.ˇ»
 6557        "},
 6558        indoc! {"
 6559            «1. This is a numbered list item that is
 6560               very long and needs to be wrapped
 6561               properly.
 6562            2. This is a numbered list item that is
 6563               very long and needs to be wrapped
 6564               properly.
 6565            - This is an unordered list item that is
 6566              also very long and should not merge
 6567              with the numbered item.ˇ»
 6568        "},
 6569        markdown_language.clone(),
 6570        &mut cx,
 6571    );
 6572
 6573    // Test that rewrapping maintain indents even when they already exists.
 6574    assert_rewrap(
 6575        indoc! {"
 6576            «1. This is a numbered list
 6577               item that is very long and needs to be wrapped properly.
 6578            2. This is a numbered list
 6579               item that is very long and needs to be wrapped properly.
 6580            - This is an unordered list item that is also very long and
 6581              should not merge with the numbered item.ˇ»
 6582        "},
 6583        indoc! {"
 6584            «1. This is a numbered list item that is
 6585               very long and needs to be wrapped
 6586               properly.
 6587            2. This is a numbered list item that is
 6588               very long and needs to be wrapped
 6589               properly.
 6590            - This is an unordered list item that is
 6591              also very long and should not merge
 6592              with the numbered item.ˇ»
 6593        "},
 6594        markdown_language,
 6595        &mut cx,
 6596    );
 6597
 6598    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6599    assert_rewrap(
 6600        indoc! {"
 6601            ˇThis is a very long line of plain text that will be wrapped.
 6602        "},
 6603        indoc! {"
 6604            ˇThis is a very long line of plain text
 6605            that will be wrapped.
 6606        "},
 6607        plaintext_language.clone(),
 6608        &mut cx,
 6609    );
 6610
 6611    // Test that non-commented code acts as a paragraph boundary within a selection
 6612    assert_rewrap(
 6613        indoc! {"
 6614               «// This is the first long comment block to be wrapped.
 6615               fn my_func(a: u32);
 6616               // This is the second long comment block to be wrapped.ˇ»
 6617           "},
 6618        indoc! {"
 6619               «// This is the first long comment block
 6620               // to be wrapped.
 6621               fn my_func(a: u32);
 6622               // This is the second long comment block
 6623               // to be wrapped.ˇ»
 6624           "},
 6625        rust_language,
 6626        &mut cx,
 6627    );
 6628
 6629    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6630    assert_rewrap(
 6631        indoc! {"
 6632            «ˇThis is a very long line that will be wrapped.
 6633
 6634            This is another paragraph in the same selection.»
 6635
 6636            «\tThis is a very long indented line that will be wrapped.ˇ»
 6637         "},
 6638        indoc! {"
 6639            «ˇThis is a very long line that will be
 6640            wrapped.
 6641
 6642            This is another paragraph in the same
 6643            selection.»
 6644
 6645            «\tThis is a very long indented line
 6646            \tthat will be wrapped.ˇ»
 6647         "},
 6648        plaintext_language,
 6649        &mut cx,
 6650    );
 6651
 6652    // Test that an empty comment line acts as a paragraph boundary
 6653    assert_rewrap(
 6654        indoc! {"
 6655            // ˇThis is a long comment that will be wrapped.
 6656            //
 6657            // And this is another long comment that will also be wrapped.ˇ
 6658         "},
 6659        indoc! {"
 6660            // ˇThis is a long comment that will be
 6661            // wrapped.
 6662            //
 6663            // And this is another long comment that
 6664            // will also be wrapped.ˇ
 6665         "},
 6666        cpp_language,
 6667        &mut cx,
 6668    );
 6669
 6670    #[track_caller]
 6671    fn assert_rewrap(
 6672        unwrapped_text: &str,
 6673        wrapped_text: &str,
 6674        language: Arc<Language>,
 6675        cx: &mut EditorTestContext,
 6676    ) {
 6677        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6678        cx.set_state(unwrapped_text);
 6679        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6680        cx.assert_editor_state(wrapped_text);
 6681    }
 6682}
 6683
 6684#[gpui::test]
 6685async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6686    init_test(cx, |settings| {
 6687        settings.languages.0.extend([(
 6688            "Rust".into(),
 6689            LanguageSettingsContent {
 6690                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6691                preferred_line_length: Some(40),
 6692                ..Default::default()
 6693            },
 6694        )])
 6695    });
 6696
 6697    let mut cx = EditorTestContext::new(cx).await;
 6698
 6699    let rust_lang = Arc::new(
 6700        Language::new(
 6701            LanguageConfig {
 6702                name: "Rust".into(),
 6703                line_comments: vec!["// ".into()],
 6704                block_comment: Some(BlockCommentConfig {
 6705                    start: "/*".into(),
 6706                    end: "*/".into(),
 6707                    prefix: "* ".into(),
 6708                    tab_size: 1,
 6709                }),
 6710                documentation_comment: Some(BlockCommentConfig {
 6711                    start: "/**".into(),
 6712                    end: "*/".into(),
 6713                    prefix: "* ".into(),
 6714                    tab_size: 1,
 6715                }),
 6716
 6717                ..LanguageConfig::default()
 6718            },
 6719            Some(tree_sitter_rust::LANGUAGE.into()),
 6720        )
 6721        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6722        .unwrap(),
 6723    );
 6724
 6725    // regular block comment
 6726    assert_rewrap(
 6727        indoc! {"
 6728            /*
 6729             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6730             */
 6731            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6732        "},
 6733        indoc! {"
 6734            /*
 6735             *ˇ Lorem ipsum dolor sit amet,
 6736             * consectetur adipiscing elit.
 6737             */
 6738            /*
 6739             *ˇ Lorem ipsum dolor sit amet,
 6740             * consectetur adipiscing elit.
 6741             */
 6742        "},
 6743        rust_lang.clone(),
 6744        &mut cx,
 6745    );
 6746
 6747    // indent is respected
 6748    assert_rewrap(
 6749        indoc! {"
 6750            {}
 6751                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6752        "},
 6753        indoc! {"
 6754            {}
 6755                /*
 6756                 *ˇ Lorem ipsum dolor sit amet,
 6757                 * consectetur adipiscing elit.
 6758                 */
 6759        "},
 6760        rust_lang.clone(),
 6761        &mut cx,
 6762    );
 6763
 6764    // short block comments with inline delimiters
 6765    assert_rewrap(
 6766        indoc! {"
 6767            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6768            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6769             */
 6770            /*
 6771             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6772        "},
 6773        indoc! {"
 6774            /*
 6775             *ˇ Lorem ipsum dolor sit amet,
 6776             * consectetur adipiscing elit.
 6777             */
 6778            /*
 6779             *ˇ Lorem ipsum dolor sit amet,
 6780             * consectetur adipiscing elit.
 6781             */
 6782            /*
 6783             *ˇ Lorem ipsum dolor sit amet,
 6784             * consectetur adipiscing elit.
 6785             */
 6786        "},
 6787        rust_lang.clone(),
 6788        &mut cx,
 6789    );
 6790
 6791    // multiline block comment with inline start/end delimiters
 6792    assert_rewrap(
 6793        indoc! {"
 6794            /*ˇ Lorem ipsum dolor sit amet,
 6795             * consectetur adipiscing elit. */
 6796        "},
 6797        indoc! {"
 6798            /*
 6799             *ˇ Lorem ipsum dolor sit amet,
 6800             * consectetur adipiscing elit.
 6801             */
 6802        "},
 6803        rust_lang.clone(),
 6804        &mut cx,
 6805    );
 6806
 6807    // block comment rewrap still respects paragraph bounds
 6808    assert_rewrap(
 6809        indoc! {"
 6810            /*
 6811             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6812             *
 6813             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6814             */
 6815        "},
 6816        indoc! {"
 6817            /*
 6818             *ˇ Lorem ipsum dolor sit amet,
 6819             * consectetur adipiscing elit.
 6820             *
 6821             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6822             */
 6823        "},
 6824        rust_lang.clone(),
 6825        &mut cx,
 6826    );
 6827
 6828    // documentation comments
 6829    assert_rewrap(
 6830        indoc! {"
 6831            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6832            /**
 6833             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6834             */
 6835        "},
 6836        indoc! {"
 6837            /**
 6838             *ˇ Lorem ipsum dolor sit amet,
 6839             * consectetur adipiscing elit.
 6840             */
 6841            /**
 6842             *ˇ Lorem ipsum dolor sit amet,
 6843             * consectetur adipiscing elit.
 6844             */
 6845        "},
 6846        rust_lang.clone(),
 6847        &mut cx,
 6848    );
 6849
 6850    // different, adjacent comments
 6851    assert_rewrap(
 6852        indoc! {"
 6853            /**
 6854             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6855             */
 6856            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6857            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6858        "},
 6859        indoc! {"
 6860            /**
 6861             *ˇ Lorem ipsum dolor sit amet,
 6862             * consectetur adipiscing elit.
 6863             */
 6864            /*
 6865             *ˇ Lorem ipsum dolor sit amet,
 6866             * consectetur adipiscing elit.
 6867             */
 6868            //ˇ Lorem ipsum dolor sit amet,
 6869            // consectetur adipiscing elit.
 6870        "},
 6871        rust_lang.clone(),
 6872        &mut cx,
 6873    );
 6874
 6875    // selection w/ single short block comment
 6876    assert_rewrap(
 6877        indoc! {"
 6878            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6879        "},
 6880        indoc! {"
 6881            «/*
 6882             * Lorem ipsum dolor sit amet,
 6883             * consectetur adipiscing elit.
 6884             */ˇ»
 6885        "},
 6886        rust_lang.clone(),
 6887        &mut cx,
 6888    );
 6889
 6890    // rewrapping a single comment w/ abutting comments
 6891    assert_rewrap(
 6892        indoc! {"
 6893            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6894            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6895        "},
 6896        indoc! {"
 6897            /*
 6898             * ˇLorem ipsum dolor sit amet,
 6899             * consectetur adipiscing elit.
 6900             */
 6901            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6902        "},
 6903        rust_lang.clone(),
 6904        &mut cx,
 6905    );
 6906
 6907    // selection w/ non-abutting short block comments
 6908    assert_rewrap(
 6909        indoc! {"
 6910            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6911
 6912            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6913        "},
 6914        indoc! {"
 6915            «/*
 6916             * Lorem ipsum dolor sit amet,
 6917             * consectetur adipiscing elit.
 6918             */
 6919
 6920            /*
 6921             * Lorem ipsum dolor sit amet,
 6922             * consectetur adipiscing elit.
 6923             */ˇ»
 6924        "},
 6925        rust_lang.clone(),
 6926        &mut cx,
 6927    );
 6928
 6929    // selection of multiline block comments
 6930    assert_rewrap(
 6931        indoc! {"
 6932            «/* Lorem ipsum dolor sit amet,
 6933             * consectetur adipiscing elit. */ˇ»
 6934        "},
 6935        indoc! {"
 6936            «/*
 6937             * Lorem ipsum dolor sit amet,
 6938             * consectetur adipiscing elit.
 6939             */ˇ»
 6940        "},
 6941        rust_lang.clone(),
 6942        &mut cx,
 6943    );
 6944
 6945    // partial selection of multiline block comments
 6946    assert_rewrap(
 6947        indoc! {"
 6948            «/* Lorem ipsum dolor sit amet,ˇ»
 6949             * consectetur adipiscing elit. */
 6950            /* Lorem ipsum dolor sit amet,
 6951             «* consectetur adipiscing elit. */ˇ»
 6952        "},
 6953        indoc! {"
 6954            «/*
 6955             * Lorem ipsum dolor sit amet,ˇ»
 6956             * consectetur adipiscing elit. */
 6957            /* Lorem ipsum dolor sit amet,
 6958             «* consectetur adipiscing elit.
 6959             */ˇ»
 6960        "},
 6961        rust_lang.clone(),
 6962        &mut cx,
 6963    );
 6964
 6965    // selection w/ abutting short block comments
 6966    // TODO: should not be combined; should rewrap as 2 comments
 6967    assert_rewrap(
 6968        indoc! {"
 6969            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6970            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6971        "},
 6972        // desired behavior:
 6973        // indoc! {"
 6974        //     «/*
 6975        //      * Lorem ipsum dolor sit amet,
 6976        //      * consectetur adipiscing elit.
 6977        //      */
 6978        //     /*
 6979        //      * Lorem ipsum dolor sit amet,
 6980        //      * consectetur adipiscing elit.
 6981        //      */ˇ»
 6982        // "},
 6983        // actual behaviour:
 6984        indoc! {"
 6985            «/*
 6986             * Lorem ipsum dolor sit amet,
 6987             * consectetur adipiscing elit. Lorem
 6988             * ipsum dolor sit amet, consectetur
 6989             * adipiscing elit.
 6990             */ˇ»
 6991        "},
 6992        rust_lang.clone(),
 6993        &mut cx,
 6994    );
 6995
 6996    // TODO: same as above, but with delimiters on separate line
 6997    // assert_rewrap(
 6998    //     indoc! {"
 6999    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7000    //          */
 7001    //         /*
 7002    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 7003    //     "},
 7004    //     // desired:
 7005    //     // indoc! {"
 7006    //     //     «/*
 7007    //     //      * Lorem ipsum dolor sit amet,
 7008    //     //      * consectetur adipiscing elit.
 7009    //     //      */
 7010    //     //     /*
 7011    //     //      * Lorem ipsum dolor sit amet,
 7012    //     //      * consectetur adipiscing elit.
 7013    //     //      */ˇ»
 7014    //     // "},
 7015    //     // actual: (but with trailing w/s on the empty lines)
 7016    //     indoc! {"
 7017    //         «/*
 7018    //          * Lorem ipsum dolor sit amet,
 7019    //          * consectetur adipiscing elit.
 7020    //          *
 7021    //          */
 7022    //         /*
 7023    //          *
 7024    //          * Lorem ipsum dolor sit amet,
 7025    //          * consectetur adipiscing elit.
 7026    //          */ˇ»
 7027    //     "},
 7028    //     rust_lang.clone(),
 7029    //     &mut cx,
 7030    // );
 7031
 7032    // TODO these are unhandled edge cases; not correct, just documenting known issues
 7033    assert_rewrap(
 7034        indoc! {"
 7035            /*
 7036             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 7037             */
 7038            /*
 7039             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 7040            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 7041        "},
 7042        // desired:
 7043        // indoc! {"
 7044        //     /*
 7045        //      *ˇ Lorem ipsum dolor sit amet,
 7046        //      * consectetur adipiscing elit.
 7047        //      */
 7048        //     /*
 7049        //      *ˇ Lorem ipsum dolor sit amet,
 7050        //      * consectetur adipiscing elit.
 7051        //      */
 7052        //     /*
 7053        //      *ˇ Lorem ipsum dolor sit amet
 7054        //      */ /* consectetur adipiscing elit. */
 7055        // "},
 7056        // actual:
 7057        indoc! {"
 7058            /*
 7059             //ˇ Lorem ipsum dolor sit amet,
 7060             // consectetur adipiscing elit.
 7061             */
 7062            /*
 7063             * //ˇ Lorem ipsum dolor sit amet,
 7064             * consectetur adipiscing elit.
 7065             */
 7066            /*
 7067             *ˇ Lorem ipsum dolor sit amet */ /*
 7068             * consectetur adipiscing elit.
 7069             */
 7070        "},
 7071        rust_lang,
 7072        &mut cx,
 7073    );
 7074
 7075    #[track_caller]
 7076    fn assert_rewrap(
 7077        unwrapped_text: &str,
 7078        wrapped_text: &str,
 7079        language: Arc<Language>,
 7080        cx: &mut EditorTestContext,
 7081    ) {
 7082        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 7083        cx.set_state(unwrapped_text);
 7084        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 7085        cx.assert_editor_state(wrapped_text);
 7086    }
 7087}
 7088
 7089#[gpui::test]
 7090async fn test_hard_wrap(cx: &mut TestAppContext) {
 7091    init_test(cx, |_| {});
 7092    let mut cx = EditorTestContext::new(cx).await;
 7093
 7094    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 7095    cx.update_editor(|editor, _, cx| {
 7096        editor.set_hard_wrap(Some(14), cx);
 7097    });
 7098
 7099    cx.set_state(indoc!(
 7100        "
 7101        one two three ˇ
 7102        "
 7103    ));
 7104    cx.simulate_input("four");
 7105    cx.run_until_parked();
 7106
 7107    cx.assert_editor_state(indoc!(
 7108        "
 7109        one two three
 7110        fourˇ
 7111        "
 7112    ));
 7113
 7114    cx.update_editor(|editor, window, cx| {
 7115        editor.newline(&Default::default(), window, cx);
 7116    });
 7117    cx.run_until_parked();
 7118    cx.assert_editor_state(indoc!(
 7119        "
 7120        one two three
 7121        four
 7122        ˇ
 7123        "
 7124    ));
 7125
 7126    cx.simulate_input("five");
 7127    cx.run_until_parked();
 7128    cx.assert_editor_state(indoc!(
 7129        "
 7130        one two three
 7131        four
 7132        fiveˇ
 7133        "
 7134    ));
 7135
 7136    cx.update_editor(|editor, window, cx| {
 7137        editor.newline(&Default::default(), window, cx);
 7138    });
 7139    cx.run_until_parked();
 7140    cx.simulate_input("# ");
 7141    cx.run_until_parked();
 7142    cx.assert_editor_state(indoc!(
 7143        "
 7144        one two three
 7145        four
 7146        five
 7147        # ˇ
 7148        "
 7149    ));
 7150
 7151    cx.update_editor(|editor, window, cx| {
 7152        editor.newline(&Default::default(), window, cx);
 7153    });
 7154    cx.run_until_parked();
 7155    cx.assert_editor_state(indoc!(
 7156        "
 7157        one two three
 7158        four
 7159        five
 7160        #\x20
 7161 7162        "
 7163    ));
 7164
 7165    cx.simulate_input(" 6");
 7166    cx.run_until_parked();
 7167    cx.assert_editor_state(indoc!(
 7168        "
 7169        one two three
 7170        four
 7171        five
 7172        #
 7173        # 6ˇ
 7174        "
 7175    ));
 7176}
 7177
 7178#[gpui::test]
 7179async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7180    init_test(cx, |_| {});
 7181
 7182    let mut cx = EditorTestContext::new(cx).await;
 7183
 7184    cx.set_state(indoc! {"The quick brownˇ"});
 7185    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7186    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7187
 7188    cx.set_state(indoc! {"The emacs foxˇ"});
 7189    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7190    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7191
 7192    cx.set_state(indoc! {"
 7193        The quick« brownˇ»
 7194        fox jumps overˇ
 7195        the lazy dog"});
 7196    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7197    cx.assert_editor_state(indoc! {"
 7198        The quickˇ
 7199        ˇthe lazy dog"});
 7200
 7201    cx.set_state(indoc! {"
 7202        The quick« brownˇ»
 7203        fox jumps overˇ
 7204        the lazy dog"});
 7205    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7206    cx.assert_editor_state(indoc! {"
 7207        The quickˇ
 7208        fox jumps overˇthe lazy dog"});
 7209
 7210    cx.set_state(indoc! {"
 7211        The quick« brownˇ»
 7212        fox jumps overˇ
 7213        the lazy dog"});
 7214    cx.update_editor(|e, window, cx| {
 7215        e.cut_to_end_of_line(
 7216            &CutToEndOfLine {
 7217                stop_at_newlines: true,
 7218            },
 7219            window,
 7220            cx,
 7221        )
 7222    });
 7223    cx.assert_editor_state(indoc! {"
 7224        The quickˇ
 7225        fox jumps overˇ
 7226        the lazy dog"});
 7227
 7228    cx.set_state(indoc! {"
 7229        The quick« brownˇ»
 7230        fox jumps overˇ
 7231        the lazy dog"});
 7232    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7233    cx.assert_editor_state(indoc! {"
 7234        The quickˇ
 7235        fox jumps overˇthe lazy dog"});
 7236}
 7237
 7238#[gpui::test]
 7239async fn test_clipboard(cx: &mut TestAppContext) {
 7240    init_test(cx, |_| {});
 7241
 7242    let mut cx = EditorTestContext::new(cx).await;
 7243
 7244    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7245    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7246    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7247
 7248    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7249    cx.set_state("two ˇfour ˇsix ˇ");
 7250    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7251    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7252
 7253    // Paste again but with only two cursors. Since the number of cursors doesn't
 7254    // match the number of slices in the clipboard, the entire clipboard text
 7255    // is pasted at each cursor.
 7256    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7257    cx.update_editor(|e, window, cx| {
 7258        e.handle_input("( ", window, cx);
 7259        e.paste(&Paste, window, cx);
 7260        e.handle_input(") ", window, cx);
 7261    });
 7262    cx.assert_editor_state(
 7263        &([
 7264            "( one✅ ",
 7265            "three ",
 7266            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7267            "three ",
 7268            "five ) ˇ",
 7269        ]
 7270        .join("\n")),
 7271    );
 7272
 7273    // Cut with three selections, one of which is full-line.
 7274    cx.set_state(indoc! {"
 7275        1«2ˇ»3
 7276        4ˇ567
 7277        «8ˇ»9"});
 7278    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7279    cx.assert_editor_state(indoc! {"
 7280        1ˇ3
 7281        ˇ9"});
 7282
 7283    // Paste with three selections, noticing how the copied selection that was full-line
 7284    // gets inserted before the second cursor.
 7285    cx.set_state(indoc! {"
 7286        1ˇ3
 7287 7288        «oˇ»ne"});
 7289    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7290    cx.assert_editor_state(indoc! {"
 7291        12ˇ3
 7292        4567
 7293 7294        8ˇne"});
 7295
 7296    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7297    cx.set_state(indoc! {"
 7298        The quick brown
 7299        fox juˇmps over
 7300        the lazy dog"});
 7301    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7302    assert_eq!(
 7303        cx.read_from_clipboard()
 7304            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7305        Some("fox jumps over\n".to_string())
 7306    );
 7307
 7308    // Paste with three selections, noticing how the copied full-line selection is inserted
 7309    // before the empty selections but replaces the selection that is non-empty.
 7310    cx.set_state(indoc! {"
 7311        Tˇhe quick brown
 7312        «foˇ»x jumps over
 7313        tˇhe lazy dog"});
 7314    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7315    cx.assert_editor_state(indoc! {"
 7316        fox jumps over
 7317        Tˇhe quick brown
 7318        fox jumps over
 7319        ˇx jumps over
 7320        fox jumps over
 7321        tˇhe lazy dog"});
 7322}
 7323
 7324#[gpui::test]
 7325async fn test_copy_trim(cx: &mut TestAppContext) {
 7326    init_test(cx, |_| {});
 7327
 7328    let mut cx = EditorTestContext::new(cx).await;
 7329    cx.set_state(
 7330        r#"            «for selection in selections.iter() {
 7331            let mut start = selection.start;
 7332            let mut end = selection.end;
 7333            let is_entire_line = selection.is_empty();
 7334            if is_entire_line {
 7335                start = Point::new(start.row, 0);ˇ»
 7336                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7337            }
 7338        "#,
 7339    );
 7340    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7341    assert_eq!(
 7342        cx.read_from_clipboard()
 7343            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7344        Some(
 7345            "for selection in selections.iter() {
 7346            let mut start = selection.start;
 7347            let mut end = selection.end;
 7348            let is_entire_line = selection.is_empty();
 7349            if is_entire_line {
 7350                start = Point::new(start.row, 0);"
 7351                .to_string()
 7352        ),
 7353        "Regular copying preserves all indentation selected",
 7354    );
 7355    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7356    assert_eq!(
 7357        cx.read_from_clipboard()
 7358            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7359        Some(
 7360            "for selection in selections.iter() {
 7361let mut start = selection.start;
 7362let mut end = selection.end;
 7363let is_entire_line = selection.is_empty();
 7364if is_entire_line {
 7365    start = Point::new(start.row, 0);"
 7366                .to_string()
 7367        ),
 7368        "Copying with stripping should strip all leading whitespaces"
 7369    );
 7370
 7371    cx.set_state(
 7372        r#"       «     for selection in selections.iter() {
 7373            let mut start = selection.start;
 7374            let mut end = selection.end;
 7375            let is_entire_line = selection.is_empty();
 7376            if is_entire_line {
 7377                start = Point::new(start.row, 0);ˇ»
 7378                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7379            }
 7380        "#,
 7381    );
 7382    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7383    assert_eq!(
 7384        cx.read_from_clipboard()
 7385            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7386        Some(
 7387            "     for selection in selections.iter() {
 7388            let mut start = selection.start;
 7389            let mut end = selection.end;
 7390            let is_entire_line = selection.is_empty();
 7391            if is_entire_line {
 7392                start = Point::new(start.row, 0);"
 7393                .to_string()
 7394        ),
 7395        "Regular copying preserves all indentation selected",
 7396    );
 7397    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7398    assert_eq!(
 7399        cx.read_from_clipboard()
 7400            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7401        Some(
 7402            "for selection in selections.iter() {
 7403let mut start = selection.start;
 7404let mut end = selection.end;
 7405let is_entire_line = selection.is_empty();
 7406if is_entire_line {
 7407    start = Point::new(start.row, 0);"
 7408                .to_string()
 7409        ),
 7410        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7411    );
 7412
 7413    cx.set_state(
 7414        r#"       «ˇ     for selection in selections.iter() {
 7415            let mut start = selection.start;
 7416            let mut end = selection.end;
 7417            let is_entire_line = selection.is_empty();
 7418            if is_entire_line {
 7419                start = Point::new(start.row, 0);»
 7420                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7421            }
 7422        "#,
 7423    );
 7424    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7425    assert_eq!(
 7426        cx.read_from_clipboard()
 7427            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7428        Some(
 7429            "     for selection in selections.iter() {
 7430            let mut start = selection.start;
 7431            let mut end = selection.end;
 7432            let is_entire_line = selection.is_empty();
 7433            if is_entire_line {
 7434                start = Point::new(start.row, 0);"
 7435                .to_string()
 7436        ),
 7437        "Regular copying for reverse selection works the same",
 7438    );
 7439    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7440    assert_eq!(
 7441        cx.read_from_clipboard()
 7442            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7443        Some(
 7444            "for selection in selections.iter() {
 7445let mut start = selection.start;
 7446let mut end = selection.end;
 7447let is_entire_line = selection.is_empty();
 7448if is_entire_line {
 7449    start = Point::new(start.row, 0);"
 7450                .to_string()
 7451        ),
 7452        "Copying with stripping for reverse selection works the same"
 7453    );
 7454
 7455    cx.set_state(
 7456        r#"            for selection «in selections.iter() {
 7457            let mut start = selection.start;
 7458            let mut end = selection.end;
 7459            let is_entire_line = selection.is_empty();
 7460            if is_entire_line {
 7461                start = Point::new(start.row, 0);ˇ»
 7462                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7463            }
 7464        "#,
 7465    );
 7466    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7467    assert_eq!(
 7468        cx.read_from_clipboard()
 7469            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7470        Some(
 7471            "in selections.iter() {
 7472            let mut start = selection.start;
 7473            let mut end = selection.end;
 7474            let is_entire_line = selection.is_empty();
 7475            if is_entire_line {
 7476                start = Point::new(start.row, 0);"
 7477                .to_string()
 7478        ),
 7479        "When selecting past the indent, the copying works as usual",
 7480    );
 7481    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7482    assert_eq!(
 7483        cx.read_from_clipboard()
 7484            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7485        Some(
 7486            "in selections.iter() {
 7487            let mut start = selection.start;
 7488            let mut end = selection.end;
 7489            let is_entire_line = selection.is_empty();
 7490            if is_entire_line {
 7491                start = Point::new(start.row, 0);"
 7492                .to_string()
 7493        ),
 7494        "When selecting past the indent, nothing is trimmed"
 7495    );
 7496
 7497    cx.set_state(
 7498        r#"            «for selection in selections.iter() {
 7499            let mut start = selection.start;
 7500
 7501            let mut end = selection.end;
 7502            let is_entire_line = selection.is_empty();
 7503            if is_entire_line {
 7504                start = Point::new(start.row, 0);
 7505ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7506            }
 7507        "#,
 7508    );
 7509    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7510    assert_eq!(
 7511        cx.read_from_clipboard()
 7512            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7513        Some(
 7514            "for selection in selections.iter() {
 7515let mut start = selection.start;
 7516
 7517let mut end = selection.end;
 7518let is_entire_line = selection.is_empty();
 7519if is_entire_line {
 7520    start = Point::new(start.row, 0);
 7521"
 7522            .to_string()
 7523        ),
 7524        "Copying with stripping should ignore empty lines"
 7525    );
 7526}
 7527
 7528#[gpui::test]
 7529async fn test_paste_multiline(cx: &mut TestAppContext) {
 7530    init_test(cx, |_| {});
 7531
 7532    let mut cx = EditorTestContext::new(cx).await;
 7533    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7534
 7535    // Cut an indented block, without the leading whitespace.
 7536    cx.set_state(indoc! {"
 7537        const a: B = (
 7538            c(),
 7539            «d(
 7540                e,
 7541                f
 7542            )ˇ»
 7543        );
 7544    "});
 7545    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7546    cx.assert_editor_state(indoc! {"
 7547        const a: B = (
 7548            c(),
 7549            ˇ
 7550        );
 7551    "});
 7552
 7553    // Paste it at the same position.
 7554    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7555    cx.assert_editor_state(indoc! {"
 7556        const a: B = (
 7557            c(),
 7558            d(
 7559                e,
 7560                f
 7561 7562        );
 7563    "});
 7564
 7565    // Paste it at a line with a lower indent level.
 7566    cx.set_state(indoc! {"
 7567        ˇ
 7568        const a: B = (
 7569            c(),
 7570        );
 7571    "});
 7572    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7573    cx.assert_editor_state(indoc! {"
 7574        d(
 7575            e,
 7576            f
 7577 7578        const a: B = (
 7579            c(),
 7580        );
 7581    "});
 7582
 7583    // Cut an indented block, with the leading whitespace.
 7584    cx.set_state(indoc! {"
 7585        const a: B = (
 7586            c(),
 7587        «    d(
 7588                e,
 7589                f
 7590            )
 7591        ˇ»);
 7592    "});
 7593    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7594    cx.assert_editor_state(indoc! {"
 7595        const a: B = (
 7596            c(),
 7597        ˇ);
 7598    "});
 7599
 7600    // Paste it at the same position.
 7601    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7602    cx.assert_editor_state(indoc! {"
 7603        const a: B = (
 7604            c(),
 7605            d(
 7606                e,
 7607                f
 7608            )
 7609        ˇ);
 7610    "});
 7611
 7612    // Paste it at a line with a higher indent level.
 7613    cx.set_state(indoc! {"
 7614        const a: B = (
 7615            c(),
 7616            d(
 7617                e,
 7618 7619            )
 7620        );
 7621    "});
 7622    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7623    cx.assert_editor_state(indoc! {"
 7624        const a: B = (
 7625            c(),
 7626            d(
 7627                e,
 7628                f    d(
 7629                    e,
 7630                    f
 7631                )
 7632        ˇ
 7633            )
 7634        );
 7635    "});
 7636
 7637    // Copy an indented block, starting mid-line
 7638    cx.set_state(indoc! {"
 7639        const a: B = (
 7640            c(),
 7641            somethin«g(
 7642                e,
 7643                f
 7644            )ˇ»
 7645        );
 7646    "});
 7647    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7648
 7649    // Paste it on a line with a lower indent level
 7650    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7651    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7652    cx.assert_editor_state(indoc! {"
 7653        const a: B = (
 7654            c(),
 7655            something(
 7656                e,
 7657                f
 7658            )
 7659        );
 7660        g(
 7661            e,
 7662            f
 7663"});
 7664}
 7665
 7666#[gpui::test]
 7667async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7668    init_test(cx, |_| {});
 7669
 7670    cx.write_to_clipboard(ClipboardItem::new_string(
 7671        "    d(\n        e\n    );\n".into(),
 7672    ));
 7673
 7674    let mut cx = EditorTestContext::new(cx).await;
 7675    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7676
 7677    cx.set_state(indoc! {"
 7678        fn a() {
 7679            b();
 7680            if c() {
 7681                ˇ
 7682            }
 7683        }
 7684    "});
 7685
 7686    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7687    cx.assert_editor_state(indoc! {"
 7688        fn a() {
 7689            b();
 7690            if c() {
 7691                d(
 7692                    e
 7693                );
 7694        ˇ
 7695            }
 7696        }
 7697    "});
 7698
 7699    cx.set_state(indoc! {"
 7700        fn a() {
 7701            b();
 7702            ˇ
 7703        }
 7704    "});
 7705
 7706    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7707    cx.assert_editor_state(indoc! {"
 7708        fn a() {
 7709            b();
 7710            d(
 7711                e
 7712            );
 7713        ˇ
 7714        }
 7715    "});
 7716}
 7717
 7718#[gpui::test]
 7719fn test_select_all(cx: &mut TestAppContext) {
 7720    init_test(cx, |_| {});
 7721
 7722    let editor = cx.add_window(|window, cx| {
 7723        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7724        build_editor(buffer, window, cx)
 7725    });
 7726    _ = editor.update(cx, |editor, window, cx| {
 7727        editor.select_all(&SelectAll, window, cx);
 7728        assert_eq!(
 7729            display_ranges(editor, cx),
 7730            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7731        );
 7732    });
 7733}
 7734
 7735#[gpui::test]
 7736fn test_select_line(cx: &mut TestAppContext) {
 7737    init_test(cx, |_| {});
 7738
 7739    let editor = cx.add_window(|window, cx| {
 7740        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7741        build_editor(buffer, window, cx)
 7742    });
 7743    _ = editor.update(cx, |editor, window, cx| {
 7744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7745            s.select_display_ranges([
 7746                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7747                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7748                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7749                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7750            ])
 7751        });
 7752        editor.select_line(&SelectLine, window, cx);
 7753        // Adjacent line selections should NOT merge (only overlapping ones do)
 7754        assert_eq!(
 7755            display_ranges(editor, cx),
 7756            vec![
 7757                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7758                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7759                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7760            ]
 7761        );
 7762    });
 7763
 7764    _ = editor.update(cx, |editor, window, cx| {
 7765        editor.select_line(&SelectLine, window, cx);
 7766        assert_eq!(
 7767            display_ranges(editor, cx),
 7768            vec![
 7769                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7770                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7771            ]
 7772        );
 7773    });
 7774
 7775    _ = editor.update(cx, |editor, window, cx| {
 7776        editor.select_line(&SelectLine, window, cx);
 7777        // Adjacent but not overlapping, so they stay separate
 7778        assert_eq!(
 7779            display_ranges(editor, cx),
 7780            vec![
 7781                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(4), 0),
 7782                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7783            ]
 7784        );
 7785    });
 7786}
 7787
 7788#[gpui::test]
 7789async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7790    init_test(cx, |_| {});
 7791    let mut cx = EditorTestContext::new(cx).await;
 7792
 7793    #[track_caller]
 7794    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7795        cx.set_state(initial_state);
 7796        cx.update_editor(|e, window, cx| {
 7797            e.split_selection_into_lines(&Default::default(), window, cx)
 7798        });
 7799        cx.assert_editor_state(expected_state);
 7800    }
 7801
 7802    // Selection starts and ends at the middle of lines, left-to-right
 7803    test(
 7804        &mut cx,
 7805        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7806        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7807    );
 7808    // Same thing, right-to-left
 7809    test(
 7810        &mut cx,
 7811        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7812        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7813    );
 7814
 7815    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7816    test(
 7817        &mut cx,
 7818        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7819        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7820    );
 7821    // Same thing, right-to-left
 7822    test(
 7823        &mut cx,
 7824        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7825        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7826    );
 7827
 7828    // Whole buffer, left-to-right, last line ends with newline
 7829    test(
 7830        &mut cx,
 7831        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7832        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7833    );
 7834    // Same thing, right-to-left
 7835    test(
 7836        &mut cx,
 7837        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7838        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7839    );
 7840
 7841    // Starts at the end of a line, ends at the start of another
 7842    test(
 7843        &mut cx,
 7844        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7845        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7846    );
 7847}
 7848
 7849#[gpui::test]
 7850async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7851    init_test(cx, |_| {});
 7852
 7853    let editor = cx.add_window(|window, cx| {
 7854        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7855        build_editor(buffer, window, cx)
 7856    });
 7857
 7858    // setup
 7859    _ = editor.update(cx, |editor, window, cx| {
 7860        editor.fold_creases(
 7861            vec![
 7862                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7863                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7864                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7865            ],
 7866            true,
 7867            window,
 7868            cx,
 7869        );
 7870        assert_eq!(
 7871            editor.display_text(cx),
 7872            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7873        );
 7874    });
 7875
 7876    _ = editor.update(cx, |editor, window, cx| {
 7877        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7878            s.select_display_ranges([
 7879                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7880                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7881                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7882                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7883            ])
 7884        });
 7885        editor.split_selection_into_lines(&Default::default(), window, cx);
 7886        assert_eq!(
 7887            editor.display_text(cx),
 7888            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7889        );
 7890    });
 7891    EditorTestContext::for_editor(editor, cx)
 7892        .await
 7893        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7894
 7895    _ = editor.update(cx, |editor, window, cx| {
 7896        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7897            s.select_display_ranges([
 7898                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7899            ])
 7900        });
 7901        editor.split_selection_into_lines(&Default::default(), window, cx);
 7902        assert_eq!(
 7903            editor.display_text(cx),
 7904            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7905        );
 7906        assert_eq!(
 7907            display_ranges(editor, cx),
 7908            [
 7909                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7910                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7911                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7912                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7913                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7914                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7915                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7916            ]
 7917        );
 7918    });
 7919    EditorTestContext::for_editor(editor, cx)
 7920        .await
 7921        .assert_editor_state(
 7922            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7923        );
 7924}
 7925
 7926#[gpui::test]
 7927async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7928    init_test(cx, |_| {});
 7929
 7930    let mut cx = EditorTestContext::new(cx).await;
 7931
 7932    cx.set_state(indoc!(
 7933        r#"abc
 7934           defˇghi
 7935
 7936           jk
 7937           nlmo
 7938           "#
 7939    ));
 7940
 7941    cx.update_editor(|editor, window, cx| {
 7942        editor.add_selection_above(&Default::default(), window, cx);
 7943    });
 7944
 7945    cx.assert_editor_state(indoc!(
 7946        r#"abcˇ
 7947           defˇghi
 7948
 7949           jk
 7950           nlmo
 7951           "#
 7952    ));
 7953
 7954    cx.update_editor(|editor, window, cx| {
 7955        editor.add_selection_above(&Default::default(), window, cx);
 7956    });
 7957
 7958    cx.assert_editor_state(indoc!(
 7959        r#"abcˇ
 7960            defˇghi
 7961
 7962            jk
 7963            nlmo
 7964            "#
 7965    ));
 7966
 7967    cx.update_editor(|editor, window, cx| {
 7968        editor.add_selection_below(&Default::default(), window, cx);
 7969    });
 7970
 7971    cx.assert_editor_state(indoc!(
 7972        r#"abc
 7973           defˇghi
 7974
 7975           jk
 7976           nlmo
 7977           "#
 7978    ));
 7979
 7980    cx.update_editor(|editor, window, cx| {
 7981        editor.undo_selection(&Default::default(), window, cx);
 7982    });
 7983
 7984    cx.assert_editor_state(indoc!(
 7985        r#"abcˇ
 7986           defˇghi
 7987
 7988           jk
 7989           nlmo
 7990           "#
 7991    ));
 7992
 7993    cx.update_editor(|editor, window, cx| {
 7994        editor.redo_selection(&Default::default(), window, cx);
 7995    });
 7996
 7997    cx.assert_editor_state(indoc!(
 7998        r#"abc
 7999           defˇghi
 8000
 8001           jk
 8002           nlmo
 8003           "#
 8004    ));
 8005
 8006    cx.update_editor(|editor, window, cx| {
 8007        editor.add_selection_below(&Default::default(), window, cx);
 8008    });
 8009
 8010    cx.assert_editor_state(indoc!(
 8011        r#"abc
 8012           defˇghi
 8013           ˇ
 8014           jk
 8015           nlmo
 8016           "#
 8017    ));
 8018
 8019    cx.update_editor(|editor, window, cx| {
 8020        editor.add_selection_below(&Default::default(), window, cx);
 8021    });
 8022
 8023    cx.assert_editor_state(indoc!(
 8024        r#"abc
 8025           defˇghi
 8026           ˇ
 8027           jkˇ
 8028           nlmo
 8029           "#
 8030    ));
 8031
 8032    cx.update_editor(|editor, window, cx| {
 8033        editor.add_selection_below(&Default::default(), window, cx);
 8034    });
 8035
 8036    cx.assert_editor_state(indoc!(
 8037        r#"abc
 8038           defˇghi
 8039           ˇ
 8040           jkˇ
 8041           nlmˇo
 8042           "#
 8043    ));
 8044
 8045    cx.update_editor(|editor, window, cx| {
 8046        editor.add_selection_below(&Default::default(), window, cx);
 8047    });
 8048
 8049    cx.assert_editor_state(indoc!(
 8050        r#"abc
 8051           defˇghi
 8052           ˇ
 8053           jkˇ
 8054           nlmˇo
 8055           ˇ"#
 8056    ));
 8057
 8058    // change selections
 8059    cx.set_state(indoc!(
 8060        r#"abc
 8061           def«ˇg»hi
 8062
 8063           jk
 8064           nlmo
 8065           "#
 8066    ));
 8067
 8068    cx.update_editor(|editor, window, cx| {
 8069        editor.add_selection_below(&Default::default(), window, cx);
 8070    });
 8071
 8072    cx.assert_editor_state(indoc!(
 8073        r#"abc
 8074           def«ˇg»hi
 8075
 8076           jk
 8077           nlm«ˇo»
 8078           "#
 8079    ));
 8080
 8081    cx.update_editor(|editor, window, cx| {
 8082        editor.add_selection_below(&Default::default(), window, cx);
 8083    });
 8084
 8085    cx.assert_editor_state(indoc!(
 8086        r#"abc
 8087           def«ˇg»hi
 8088
 8089           jk
 8090           nlm«ˇo»
 8091           "#
 8092    ));
 8093
 8094    cx.update_editor(|editor, window, cx| {
 8095        editor.add_selection_above(&Default::default(), window, cx);
 8096    });
 8097
 8098    cx.assert_editor_state(indoc!(
 8099        r#"abc
 8100           def«ˇg»hi
 8101
 8102           jk
 8103           nlmo
 8104           "#
 8105    ));
 8106
 8107    cx.update_editor(|editor, window, cx| {
 8108        editor.add_selection_above(&Default::default(), window, cx);
 8109    });
 8110
 8111    cx.assert_editor_state(indoc!(
 8112        r#"abc
 8113           def«ˇg»hi
 8114
 8115           jk
 8116           nlmo
 8117           "#
 8118    ));
 8119
 8120    // Change selections again
 8121    cx.set_state(indoc!(
 8122        r#"a«bc
 8123           defgˇ»hi
 8124
 8125           jk
 8126           nlmo
 8127           "#
 8128    ));
 8129
 8130    cx.update_editor(|editor, window, cx| {
 8131        editor.add_selection_below(&Default::default(), window, cx);
 8132    });
 8133
 8134    cx.assert_editor_state(indoc!(
 8135        r#"a«bcˇ»
 8136           d«efgˇ»hi
 8137
 8138           j«kˇ»
 8139           nlmo
 8140           "#
 8141    ));
 8142
 8143    cx.update_editor(|editor, window, cx| {
 8144        editor.add_selection_below(&Default::default(), window, cx);
 8145    });
 8146    cx.assert_editor_state(indoc!(
 8147        r#"a«bcˇ»
 8148           d«efgˇ»hi
 8149
 8150           j«kˇ»
 8151           n«lmoˇ»
 8152           "#
 8153    ));
 8154    cx.update_editor(|editor, window, cx| {
 8155        editor.add_selection_above(&Default::default(), window, cx);
 8156    });
 8157
 8158    cx.assert_editor_state(indoc!(
 8159        r#"a«bcˇ»
 8160           d«efgˇ»hi
 8161
 8162           j«kˇ»
 8163           nlmo
 8164           "#
 8165    ));
 8166
 8167    // Change selections again
 8168    cx.set_state(indoc!(
 8169        r#"abc
 8170           d«ˇefghi
 8171
 8172           jk
 8173           nlm»o
 8174           "#
 8175    ));
 8176
 8177    cx.update_editor(|editor, window, cx| {
 8178        editor.add_selection_above(&Default::default(), window, cx);
 8179    });
 8180
 8181    cx.assert_editor_state(indoc!(
 8182        r#"a«ˇbc»
 8183           d«ˇef»ghi
 8184
 8185           j«ˇk»
 8186           n«ˇlm»o
 8187           "#
 8188    ));
 8189
 8190    cx.update_editor(|editor, window, cx| {
 8191        editor.add_selection_below(&Default::default(), window, cx);
 8192    });
 8193
 8194    cx.assert_editor_state(indoc!(
 8195        r#"abc
 8196           d«ˇef»ghi
 8197
 8198           j«ˇk»
 8199           n«ˇlm»o
 8200           "#
 8201    ));
 8202}
 8203
 8204#[gpui::test]
 8205async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8206    init_test(cx, |_| {});
 8207    let mut cx = EditorTestContext::new(cx).await;
 8208
 8209    cx.set_state(indoc!(
 8210        r#"line onˇe
 8211           liˇne two
 8212           line three
 8213           line four"#
 8214    ));
 8215
 8216    cx.update_editor(|editor, window, cx| {
 8217        editor.add_selection_below(&Default::default(), window, cx);
 8218    });
 8219
 8220    // test multiple cursors expand in the same direction
 8221    cx.assert_editor_state(indoc!(
 8222        r#"line onˇe
 8223           liˇne twˇo
 8224           liˇne three
 8225           line four"#
 8226    ));
 8227
 8228    cx.update_editor(|editor, window, cx| {
 8229        editor.add_selection_below(&Default::default(), window, cx);
 8230    });
 8231
 8232    cx.update_editor(|editor, window, cx| {
 8233        editor.add_selection_below(&Default::default(), window, cx);
 8234    });
 8235
 8236    // test multiple cursors expand below overflow
 8237    cx.assert_editor_state(indoc!(
 8238        r#"line onˇe
 8239           liˇne twˇo
 8240           liˇne thˇree
 8241           liˇne foˇur"#
 8242    ));
 8243
 8244    cx.update_editor(|editor, window, cx| {
 8245        editor.add_selection_above(&Default::default(), window, cx);
 8246    });
 8247
 8248    // test multiple cursors retrieves back correctly
 8249    cx.assert_editor_state(indoc!(
 8250        r#"line onˇe
 8251           liˇne twˇo
 8252           liˇne thˇree
 8253           line four"#
 8254    ));
 8255
 8256    cx.update_editor(|editor, window, cx| {
 8257        editor.add_selection_above(&Default::default(), window, cx);
 8258    });
 8259
 8260    cx.update_editor(|editor, window, cx| {
 8261        editor.add_selection_above(&Default::default(), window, cx);
 8262    });
 8263
 8264    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8265    cx.assert_editor_state(indoc!(
 8266        r#"liˇne onˇe
 8267           liˇne two
 8268           line three
 8269           line four"#
 8270    ));
 8271
 8272    cx.update_editor(|editor, window, cx| {
 8273        editor.undo_selection(&Default::default(), window, cx);
 8274    });
 8275
 8276    // test undo
 8277    cx.assert_editor_state(indoc!(
 8278        r#"line onˇe
 8279           liˇne twˇo
 8280           line three
 8281           line four"#
 8282    ));
 8283
 8284    cx.update_editor(|editor, window, cx| {
 8285        editor.redo_selection(&Default::default(), window, cx);
 8286    });
 8287
 8288    // test redo
 8289    cx.assert_editor_state(indoc!(
 8290        r#"liˇne onˇe
 8291           liˇne two
 8292           line three
 8293           line four"#
 8294    ));
 8295
 8296    cx.set_state(indoc!(
 8297        r#"abcd
 8298           ef«ghˇ»
 8299           ijkl
 8300           «mˇ»nop"#
 8301    ));
 8302
 8303    cx.update_editor(|editor, window, cx| {
 8304        editor.add_selection_above(&Default::default(), window, cx);
 8305    });
 8306
 8307    // test multiple selections expand in the same direction
 8308    cx.assert_editor_state(indoc!(
 8309        r#"ab«cdˇ»
 8310           ef«ghˇ»
 8311           «iˇ»jkl
 8312           «mˇ»nop"#
 8313    ));
 8314
 8315    cx.update_editor(|editor, window, cx| {
 8316        editor.add_selection_above(&Default::default(), window, cx);
 8317    });
 8318
 8319    // test multiple selection upward overflow
 8320    cx.assert_editor_state(indoc!(
 8321        r#"ab«cdˇ»
 8322           «eˇ»f«ghˇ»
 8323           «iˇ»jkl
 8324           «mˇ»nop"#
 8325    ));
 8326
 8327    cx.update_editor(|editor, window, cx| {
 8328        editor.add_selection_below(&Default::default(), window, cx);
 8329    });
 8330
 8331    // test multiple selection retrieves back correctly
 8332    cx.assert_editor_state(indoc!(
 8333        r#"abcd
 8334           ef«ghˇ»
 8335           «iˇ»jkl
 8336           «mˇ»nop"#
 8337    ));
 8338
 8339    cx.update_editor(|editor, window, cx| {
 8340        editor.add_selection_below(&Default::default(), window, cx);
 8341    });
 8342
 8343    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8344    cx.assert_editor_state(indoc!(
 8345        r#"abcd
 8346           ef«ghˇ»
 8347           ij«klˇ»
 8348           «mˇ»nop"#
 8349    ));
 8350
 8351    cx.update_editor(|editor, window, cx| {
 8352        editor.undo_selection(&Default::default(), window, cx);
 8353    });
 8354
 8355    // test undo
 8356    cx.assert_editor_state(indoc!(
 8357        r#"abcd
 8358           ef«ghˇ»
 8359           «iˇ»jkl
 8360           «mˇ»nop"#
 8361    ));
 8362
 8363    cx.update_editor(|editor, window, cx| {
 8364        editor.redo_selection(&Default::default(), window, cx);
 8365    });
 8366
 8367    // test redo
 8368    cx.assert_editor_state(indoc!(
 8369        r#"abcd
 8370           ef«ghˇ»
 8371           ij«klˇ»
 8372           «mˇ»nop"#
 8373    ));
 8374}
 8375
 8376#[gpui::test]
 8377async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8378    init_test(cx, |_| {});
 8379    let mut cx = EditorTestContext::new(cx).await;
 8380
 8381    cx.set_state(indoc!(
 8382        r#"line onˇe
 8383           liˇne two
 8384           line three
 8385           line four"#
 8386    ));
 8387
 8388    cx.update_editor(|editor, window, cx| {
 8389        editor.add_selection_below(&Default::default(), window, cx);
 8390        editor.add_selection_below(&Default::default(), window, cx);
 8391        editor.add_selection_below(&Default::default(), window, cx);
 8392    });
 8393
 8394    // initial state with two multi cursor groups
 8395    cx.assert_editor_state(indoc!(
 8396        r#"line onˇe
 8397           liˇne twˇo
 8398           liˇne thˇree
 8399           liˇne foˇur"#
 8400    ));
 8401
 8402    // add single cursor in middle - simulate opt click
 8403    cx.update_editor(|editor, window, cx| {
 8404        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8405        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8406        editor.end_selection(window, cx);
 8407    });
 8408
 8409    cx.assert_editor_state(indoc!(
 8410        r#"line onˇe
 8411           liˇne twˇo
 8412           liˇneˇ thˇree
 8413           liˇne foˇur"#
 8414    ));
 8415
 8416    cx.update_editor(|editor, window, cx| {
 8417        editor.add_selection_above(&Default::default(), window, cx);
 8418    });
 8419
 8420    // test new added selection expands above and existing selection shrinks
 8421    cx.assert_editor_state(indoc!(
 8422        r#"line onˇe
 8423           liˇneˇ twˇo
 8424           liˇneˇ thˇree
 8425           line four"#
 8426    ));
 8427
 8428    cx.update_editor(|editor, window, cx| {
 8429        editor.add_selection_above(&Default::default(), window, cx);
 8430    });
 8431
 8432    // test new added selection expands above and existing selection shrinks
 8433    cx.assert_editor_state(indoc!(
 8434        r#"lineˇ onˇe
 8435           liˇneˇ twˇo
 8436           lineˇ three
 8437           line four"#
 8438    ));
 8439
 8440    // intial state with two selection groups
 8441    cx.set_state(indoc!(
 8442        r#"abcd
 8443           ef«ghˇ»
 8444           ijkl
 8445           «mˇ»nop"#
 8446    ));
 8447
 8448    cx.update_editor(|editor, window, cx| {
 8449        editor.add_selection_above(&Default::default(), window, cx);
 8450        editor.add_selection_above(&Default::default(), window, cx);
 8451    });
 8452
 8453    cx.assert_editor_state(indoc!(
 8454        r#"ab«cdˇ»
 8455           «eˇ»f«ghˇ»
 8456           «iˇ»jkl
 8457           «mˇ»nop"#
 8458    ));
 8459
 8460    // add single selection in middle - simulate opt drag
 8461    cx.update_editor(|editor, window, cx| {
 8462        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8463        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8464        editor.update_selection(
 8465            DisplayPoint::new(DisplayRow(2), 4),
 8466            0,
 8467            gpui::Point::<f32>::default(),
 8468            window,
 8469            cx,
 8470        );
 8471        editor.end_selection(window, cx);
 8472    });
 8473
 8474    cx.assert_editor_state(indoc!(
 8475        r#"ab«cdˇ»
 8476           «eˇ»f«ghˇ»
 8477           «iˇ»jk«lˇ»
 8478           «mˇ»nop"#
 8479    ));
 8480
 8481    cx.update_editor(|editor, window, cx| {
 8482        editor.add_selection_below(&Default::default(), window, cx);
 8483    });
 8484
 8485    // test new added selection expands below, others shrinks from above
 8486    cx.assert_editor_state(indoc!(
 8487        r#"abcd
 8488           ef«ghˇ»
 8489           «iˇ»jk«lˇ»
 8490           «mˇ»no«pˇ»"#
 8491    ));
 8492}
 8493
 8494#[gpui::test]
 8495async fn test_select_next(cx: &mut TestAppContext) {
 8496    init_test(cx, |_| {});
 8497    let mut cx = EditorTestContext::new(cx).await;
 8498
 8499    // Enable case sensitive search.
 8500    update_test_editor_settings(&mut cx, |settings| {
 8501        let mut search_settings = SearchSettingsContent::default();
 8502        search_settings.case_sensitive = Some(true);
 8503        settings.search = Some(search_settings);
 8504    });
 8505
 8506    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8507
 8508    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8509        .unwrap();
 8510    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8511
 8512    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8513        .unwrap();
 8514    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8515
 8516    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8517    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8518
 8519    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8520    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8521
 8522    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8523        .unwrap();
 8524    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8525
 8526    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8527        .unwrap();
 8528    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8529
 8530    // Test selection direction should be preserved
 8531    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8532
 8533    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8534        .unwrap();
 8535    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8536
 8537    // Test case sensitivity
 8538    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8539    cx.update_editor(|e, window, cx| {
 8540        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8541    });
 8542    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8543
 8544    // Disable case sensitive search.
 8545    update_test_editor_settings(&mut cx, |settings| {
 8546        let mut search_settings = SearchSettingsContent::default();
 8547        search_settings.case_sensitive = Some(false);
 8548        settings.search = Some(search_settings);
 8549    });
 8550
 8551    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8552    cx.update_editor(|e, window, cx| {
 8553        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8554        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8555    });
 8556    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8557}
 8558
 8559#[gpui::test]
 8560async fn test_select_all_matches(cx: &mut TestAppContext) {
 8561    init_test(cx, |_| {});
 8562    let mut cx = EditorTestContext::new(cx).await;
 8563
 8564    // Enable case sensitive search.
 8565    update_test_editor_settings(&mut cx, |settings| {
 8566        let mut search_settings = SearchSettingsContent::default();
 8567        search_settings.case_sensitive = Some(true);
 8568        settings.search = Some(search_settings);
 8569    });
 8570
 8571    // Test caret-only selections
 8572    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8573    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8574        .unwrap();
 8575    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8576
 8577    // Test left-to-right selections
 8578    cx.set_state("abc\n«abcˇ»\nabc");
 8579    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8580        .unwrap();
 8581    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8582
 8583    // Test right-to-left selections
 8584    cx.set_state("abc\n«ˇabc»\nabc");
 8585    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8586        .unwrap();
 8587    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8588
 8589    // Test selecting whitespace with caret selection
 8590    cx.set_state("abc\nˇ   abc\nabc");
 8591    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8592        .unwrap();
 8593    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8594
 8595    // Test selecting whitespace with left-to-right selection
 8596    cx.set_state("abc\n«ˇ  »abc\nabc");
 8597    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8598        .unwrap();
 8599    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8600
 8601    // Test no matches with right-to-left selection
 8602    cx.set_state("abc\n«  ˇ»abc\nabc");
 8603    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8604        .unwrap();
 8605    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8606
 8607    // Test with a single word and clip_at_line_ends=true (#29823)
 8608    cx.set_state("aˇbc");
 8609    cx.update_editor(|e, window, cx| {
 8610        e.set_clip_at_line_ends(true, cx);
 8611        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8612        e.set_clip_at_line_ends(false, cx);
 8613    });
 8614    cx.assert_editor_state("«abcˇ»");
 8615
 8616    // Test case sensitivity
 8617    cx.set_state("fˇoo\nFOO\nFoo");
 8618    cx.update_editor(|e, window, cx| {
 8619        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8620    });
 8621    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8622
 8623    // Disable case sensitive search.
 8624    update_test_editor_settings(&mut cx, |settings| {
 8625        let mut search_settings = SearchSettingsContent::default();
 8626        search_settings.case_sensitive = Some(false);
 8627        settings.search = Some(search_settings);
 8628    });
 8629
 8630    cx.set_state("fˇoo\nFOO\nFoo");
 8631    cx.update_editor(|e, window, cx| {
 8632        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8633    });
 8634    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8635}
 8636
 8637#[gpui::test]
 8638async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8639    init_test(cx, |_| {});
 8640
 8641    let mut cx = EditorTestContext::new(cx).await;
 8642
 8643    let large_body_1 = "\nd".repeat(200);
 8644    let large_body_2 = "\ne".repeat(200);
 8645
 8646    cx.set_state(&format!(
 8647        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8648    ));
 8649    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8650        let scroll_position = editor.scroll_position(cx);
 8651        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8652        scroll_position
 8653    });
 8654
 8655    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8656        .unwrap();
 8657    cx.assert_editor_state(&format!(
 8658        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8659    ));
 8660    let scroll_position_after_selection =
 8661        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8662    assert_eq!(
 8663        initial_scroll_position, scroll_position_after_selection,
 8664        "Scroll position should not change after selecting all matches"
 8665    );
 8666}
 8667
 8668#[gpui::test]
 8669async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8670    init_test(cx, |_| {});
 8671
 8672    let mut cx = EditorLspTestContext::new_rust(
 8673        lsp::ServerCapabilities {
 8674            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8675            ..Default::default()
 8676        },
 8677        cx,
 8678    )
 8679    .await;
 8680
 8681    cx.set_state(indoc! {"
 8682        line 1
 8683        line 2
 8684        linˇe 3
 8685        line 4
 8686        line 5
 8687    "});
 8688
 8689    // Make an edit
 8690    cx.update_editor(|editor, window, cx| {
 8691        editor.handle_input("X", window, cx);
 8692    });
 8693
 8694    // Move cursor to a different position
 8695    cx.update_editor(|editor, window, cx| {
 8696        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8697            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8698        });
 8699    });
 8700
 8701    cx.assert_editor_state(indoc! {"
 8702        line 1
 8703        line 2
 8704        linXe 3
 8705        line 4
 8706        liˇne 5
 8707    "});
 8708
 8709    cx.lsp
 8710        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8711            Ok(Some(vec![lsp::TextEdit::new(
 8712                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8713                "PREFIX ".to_string(),
 8714            )]))
 8715        });
 8716
 8717    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8718        .unwrap()
 8719        .await
 8720        .unwrap();
 8721
 8722    cx.assert_editor_state(indoc! {"
 8723        PREFIX line 1
 8724        line 2
 8725        linXe 3
 8726        line 4
 8727        liˇne 5
 8728    "});
 8729
 8730    // Undo formatting
 8731    cx.update_editor(|editor, window, cx| {
 8732        editor.undo(&Default::default(), window, cx);
 8733    });
 8734
 8735    // Verify cursor moved back to position after edit
 8736    cx.assert_editor_state(indoc! {"
 8737        line 1
 8738        line 2
 8739        linXˇe 3
 8740        line 4
 8741        line 5
 8742    "});
 8743}
 8744
 8745#[gpui::test]
 8746async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8747    init_test(cx, |_| {});
 8748
 8749    let mut cx = EditorTestContext::new(cx).await;
 8750
 8751    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8752    cx.update_editor(|editor, window, cx| {
 8753        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8754    });
 8755
 8756    cx.set_state(indoc! {"
 8757        line 1
 8758        line 2
 8759        linˇe 3
 8760        line 4
 8761        line 5
 8762        line 6
 8763        line 7
 8764        line 8
 8765        line 9
 8766        line 10
 8767    "});
 8768
 8769    let snapshot = cx.buffer_snapshot();
 8770    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8771
 8772    cx.update(|_, cx| {
 8773        provider.update(cx, |provider, _| {
 8774            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 8775                id: None,
 8776                edits: vec![(edit_position..edit_position, "X".into())],
 8777                edit_preview: None,
 8778            }))
 8779        })
 8780    });
 8781
 8782    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8783    cx.update_editor(|editor, window, cx| {
 8784        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8785    });
 8786
 8787    cx.assert_editor_state(indoc! {"
 8788        line 1
 8789        line 2
 8790        lineXˇ 3
 8791        line 4
 8792        line 5
 8793        line 6
 8794        line 7
 8795        line 8
 8796        line 9
 8797        line 10
 8798    "});
 8799
 8800    cx.update_editor(|editor, window, cx| {
 8801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8802            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8803        });
 8804    });
 8805
 8806    cx.assert_editor_state(indoc! {"
 8807        line 1
 8808        line 2
 8809        lineX 3
 8810        line 4
 8811        line 5
 8812        line 6
 8813        line 7
 8814        line 8
 8815        line 9
 8816        liˇne 10
 8817    "});
 8818
 8819    cx.update_editor(|editor, window, cx| {
 8820        editor.undo(&Default::default(), window, cx);
 8821    });
 8822
 8823    cx.assert_editor_state(indoc! {"
 8824        line 1
 8825        line 2
 8826        lineˇ 3
 8827        line 4
 8828        line 5
 8829        line 6
 8830        line 7
 8831        line 8
 8832        line 9
 8833        line 10
 8834    "});
 8835}
 8836
 8837#[gpui::test]
 8838async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8839    init_test(cx, |_| {});
 8840
 8841    let mut cx = EditorTestContext::new(cx).await;
 8842    cx.set_state(
 8843        r#"let foo = 2;
 8844lˇet foo = 2;
 8845let fooˇ = 2;
 8846let foo = 2;
 8847let foo = ˇ2;"#,
 8848    );
 8849
 8850    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8851        .unwrap();
 8852    cx.assert_editor_state(
 8853        r#"let foo = 2;
 8854«letˇ» foo = 2;
 8855let «fooˇ» = 2;
 8856let foo = 2;
 8857let foo = «2ˇ»;"#,
 8858    );
 8859
 8860    // noop for multiple selections with different contents
 8861    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8862        .unwrap();
 8863    cx.assert_editor_state(
 8864        r#"let foo = 2;
 8865«letˇ» foo = 2;
 8866let «fooˇ» = 2;
 8867let foo = 2;
 8868let foo = «2ˇ»;"#,
 8869    );
 8870
 8871    // Test last selection direction should be preserved
 8872    cx.set_state(
 8873        r#"let foo = 2;
 8874let foo = 2;
 8875let «fooˇ» = 2;
 8876let «ˇfoo» = 2;
 8877let foo = 2;"#,
 8878    );
 8879
 8880    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8881        .unwrap();
 8882    cx.assert_editor_state(
 8883        r#"let foo = 2;
 8884let foo = 2;
 8885let «fooˇ» = 2;
 8886let «ˇfoo» = 2;
 8887let «ˇfoo» = 2;"#,
 8888    );
 8889}
 8890
 8891#[gpui::test]
 8892async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8893    init_test(cx, |_| {});
 8894
 8895    let mut cx =
 8896        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8897
 8898    cx.assert_editor_state(indoc! {"
 8899        ˇbbb
 8900        ccc
 8901
 8902        bbb
 8903        ccc
 8904        "});
 8905    cx.dispatch_action(SelectPrevious::default());
 8906    cx.assert_editor_state(indoc! {"
 8907                «bbbˇ»
 8908                ccc
 8909
 8910                bbb
 8911                ccc
 8912                "});
 8913    cx.dispatch_action(SelectPrevious::default());
 8914    cx.assert_editor_state(indoc! {"
 8915                «bbbˇ»
 8916                ccc
 8917
 8918                «bbbˇ»
 8919                ccc
 8920                "});
 8921}
 8922
 8923#[gpui::test]
 8924async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8925    init_test(cx, |_| {});
 8926
 8927    let mut cx = EditorTestContext::new(cx).await;
 8928    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8929
 8930    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8931        .unwrap();
 8932    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8933
 8934    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8935        .unwrap();
 8936    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8937
 8938    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8939    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8940
 8941    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8942    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8943
 8944    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8945        .unwrap();
 8946    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8947
 8948    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8949        .unwrap();
 8950    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8951}
 8952
 8953#[gpui::test]
 8954async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8955    init_test(cx, |_| {});
 8956
 8957    let mut cx = EditorTestContext::new(cx).await;
 8958    cx.set_state("");
 8959
 8960    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8961        .unwrap();
 8962    cx.assert_editor_state("«aˇ»");
 8963    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8964        .unwrap();
 8965    cx.assert_editor_state("«aˇ»");
 8966}
 8967
 8968#[gpui::test]
 8969async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8970    init_test(cx, |_| {});
 8971
 8972    let mut cx = EditorTestContext::new(cx).await;
 8973    cx.set_state(
 8974        r#"let foo = 2;
 8975lˇet foo = 2;
 8976let fooˇ = 2;
 8977let foo = 2;
 8978let foo = ˇ2;"#,
 8979    );
 8980
 8981    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8982        .unwrap();
 8983    cx.assert_editor_state(
 8984        r#"let foo = 2;
 8985«letˇ» foo = 2;
 8986let «fooˇ» = 2;
 8987let foo = 2;
 8988let foo = «2ˇ»;"#,
 8989    );
 8990
 8991    // noop for multiple selections with different contents
 8992    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8993        .unwrap();
 8994    cx.assert_editor_state(
 8995        r#"let foo = 2;
 8996«letˇ» foo = 2;
 8997let «fooˇ» = 2;
 8998let foo = 2;
 8999let foo = «2ˇ»;"#,
 9000    );
 9001}
 9002
 9003#[gpui::test]
 9004async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 9005    init_test(cx, |_| {});
 9006    let mut cx = EditorTestContext::new(cx).await;
 9007
 9008    // Enable case sensitive search.
 9009    update_test_editor_settings(&mut cx, |settings| {
 9010        let mut search_settings = SearchSettingsContent::default();
 9011        search_settings.case_sensitive = Some(true);
 9012        settings.search = Some(search_settings);
 9013    });
 9014
 9015    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9016
 9017    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9018        .unwrap();
 9019    // selection direction is preserved
 9020    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9021
 9022    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9023        .unwrap();
 9024    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9025
 9026    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9027    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9028
 9029    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9030    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9031
 9032    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9033        .unwrap();
 9034    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9035
 9036    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9037        .unwrap();
 9038    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9039
 9040    // Test case sensitivity
 9041    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9042    cx.update_editor(|e, window, cx| {
 9043        e.select_previous(&SelectPrevious::default(), window, cx)
 9044            .unwrap();
 9045        e.select_previous(&SelectPrevious::default(), window, cx)
 9046            .unwrap();
 9047    });
 9048    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9049
 9050    // Disable case sensitive search.
 9051    update_test_editor_settings(&mut cx, |settings| {
 9052        let mut search_settings = SearchSettingsContent::default();
 9053        search_settings.case_sensitive = Some(false);
 9054        settings.search = Some(search_settings);
 9055    });
 9056
 9057    cx.set_state("foo\nFOO\n«ˇFoo»");
 9058    cx.update_editor(|e, window, cx| {
 9059        e.select_previous(&SelectPrevious::default(), window, cx)
 9060            .unwrap();
 9061        e.select_previous(&SelectPrevious::default(), window, cx)
 9062            .unwrap();
 9063    });
 9064    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9065}
 9066
 9067#[gpui::test]
 9068async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9069    init_test(cx, |_| {});
 9070
 9071    let language = Arc::new(Language::new(
 9072        LanguageConfig::default(),
 9073        Some(tree_sitter_rust::LANGUAGE.into()),
 9074    ));
 9075
 9076    let text = r#"
 9077        use mod1::mod2::{mod3, mod4};
 9078
 9079        fn fn_1(param1: bool, param2: &str) {
 9080            let var1 = "text";
 9081        }
 9082    "#
 9083    .unindent();
 9084
 9085    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9086    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9087    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9088
 9089    editor
 9090        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9091        .await;
 9092
 9093    editor.update_in(cx, |editor, window, cx| {
 9094        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9095            s.select_display_ranges([
 9096                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9097                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9098                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9099            ]);
 9100        });
 9101        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9102    });
 9103    editor.update(cx, |editor, cx| {
 9104        assert_text_with_selections(
 9105            editor,
 9106            indoc! {r#"
 9107                use mod1::mod2::{mod3, «mod4ˇ»};
 9108
 9109                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9110                    let var1 = "«ˇtext»";
 9111                }
 9112            "#},
 9113            cx,
 9114        );
 9115    });
 9116
 9117    editor.update_in(cx, |editor, window, cx| {
 9118        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9119    });
 9120    editor.update(cx, |editor, cx| {
 9121        assert_text_with_selections(
 9122            editor,
 9123            indoc! {r#"
 9124                use mod1::mod2::«{mod3, mod4}ˇ»;
 9125
 9126                «ˇfn fn_1(param1: bool, param2: &str) {
 9127                    let var1 = "text";
 9128 9129            "#},
 9130            cx,
 9131        );
 9132    });
 9133
 9134    editor.update_in(cx, |editor, window, cx| {
 9135        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9136    });
 9137    assert_eq!(
 9138        editor.update(cx, |editor, cx| editor
 9139            .selections
 9140            .display_ranges(&editor.display_snapshot(cx))),
 9141        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9142    );
 9143
 9144    // Trying to expand the selected syntax node one more time has no effect.
 9145    editor.update_in(cx, |editor, window, cx| {
 9146        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9147    });
 9148    assert_eq!(
 9149        editor.update(cx, |editor, cx| editor
 9150            .selections
 9151            .display_ranges(&editor.display_snapshot(cx))),
 9152        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9153    );
 9154
 9155    editor.update_in(cx, |editor, window, cx| {
 9156        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9157    });
 9158    editor.update(cx, |editor, cx| {
 9159        assert_text_with_selections(
 9160            editor,
 9161            indoc! {r#"
 9162                use mod1::mod2::«{mod3, mod4}ˇ»;
 9163
 9164                «ˇfn fn_1(param1: bool, param2: &str) {
 9165                    let var1 = "text";
 9166 9167            "#},
 9168            cx,
 9169        );
 9170    });
 9171
 9172    editor.update_in(cx, |editor, window, cx| {
 9173        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9174    });
 9175    editor.update(cx, |editor, cx| {
 9176        assert_text_with_selections(
 9177            editor,
 9178            indoc! {r#"
 9179                use mod1::mod2::{mod3, «mod4ˇ»};
 9180
 9181                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9182                    let var1 = "«ˇtext»";
 9183                }
 9184            "#},
 9185            cx,
 9186        );
 9187    });
 9188
 9189    editor.update_in(cx, |editor, window, cx| {
 9190        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9191    });
 9192    editor.update(cx, |editor, cx| {
 9193        assert_text_with_selections(
 9194            editor,
 9195            indoc! {r#"
 9196                use mod1::mod2::{mod3, moˇd4};
 9197
 9198                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9199                    let var1 = "teˇxt";
 9200                }
 9201            "#},
 9202            cx,
 9203        );
 9204    });
 9205
 9206    // Trying to shrink the selected syntax node one more time has no effect.
 9207    editor.update_in(cx, |editor, window, cx| {
 9208        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9209    });
 9210    editor.update_in(cx, |editor, _, cx| {
 9211        assert_text_with_selections(
 9212            editor,
 9213            indoc! {r#"
 9214                use mod1::mod2::{mod3, moˇd4};
 9215
 9216                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9217                    let var1 = "teˇxt";
 9218                }
 9219            "#},
 9220            cx,
 9221        );
 9222    });
 9223
 9224    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9225    // a fold.
 9226    editor.update_in(cx, |editor, window, cx| {
 9227        editor.fold_creases(
 9228            vec![
 9229                Crease::simple(
 9230                    Point::new(0, 21)..Point::new(0, 24),
 9231                    FoldPlaceholder::test(),
 9232                ),
 9233                Crease::simple(
 9234                    Point::new(3, 20)..Point::new(3, 22),
 9235                    FoldPlaceholder::test(),
 9236                ),
 9237            ],
 9238            true,
 9239            window,
 9240            cx,
 9241        );
 9242        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9243    });
 9244    editor.update(cx, |editor, cx| {
 9245        assert_text_with_selections(
 9246            editor,
 9247            indoc! {r#"
 9248                use mod1::mod2::«{mod3, mod4}ˇ»;
 9249
 9250                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9251                    let var1 = "«ˇtext»";
 9252                }
 9253            "#},
 9254            cx,
 9255        );
 9256    });
 9257}
 9258
 9259#[gpui::test]
 9260async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9261    init_test(cx, |_| {});
 9262
 9263    let language = Arc::new(Language::new(
 9264        LanguageConfig::default(),
 9265        Some(tree_sitter_rust::LANGUAGE.into()),
 9266    ));
 9267
 9268    let text = "let a = 2;";
 9269
 9270    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9271    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9272    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9273
 9274    editor
 9275        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9276        .await;
 9277
 9278    // Test case 1: Cursor at end of word
 9279    editor.update_in(cx, |editor, window, cx| {
 9280        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9281            s.select_display_ranges([
 9282                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9283            ]);
 9284        });
 9285    });
 9286    editor.update(cx, |editor, cx| {
 9287        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 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(editor, "let «ˇa» = 2;", cx);
 9294    });
 9295    editor.update_in(cx, |editor, window, cx| {
 9296        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9297    });
 9298    editor.update(cx, |editor, cx| {
 9299        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9300    });
 9301
 9302    // Test case 2: Cursor at end of statement
 9303    editor.update_in(cx, |editor, window, cx| {
 9304        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9305            s.select_display_ranges([
 9306                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9307            ]);
 9308        });
 9309    });
 9310    editor.update(cx, |editor, cx| {
 9311        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9312    });
 9313    editor.update_in(cx, |editor, window, cx| {
 9314        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9315    });
 9316    editor.update(cx, |editor, cx| {
 9317        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9318    });
 9319}
 9320
 9321#[gpui::test]
 9322async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9323    init_test(cx, |_| {});
 9324
 9325    let language = Arc::new(Language::new(
 9326        LanguageConfig {
 9327            name: "JavaScript".into(),
 9328            ..Default::default()
 9329        },
 9330        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9331    ));
 9332
 9333    let text = r#"
 9334        let a = {
 9335            key: "value",
 9336        };
 9337    "#
 9338    .unindent();
 9339
 9340    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9341    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9342    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9343
 9344    editor
 9345        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9346        .await;
 9347
 9348    // Test case 1: Cursor after '{'
 9349    editor.update_in(cx, |editor, window, cx| {
 9350        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9351            s.select_display_ranges([
 9352                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9353            ]);
 9354        });
 9355    });
 9356    editor.update(cx, |editor, cx| {
 9357        assert_text_with_selections(
 9358            editor,
 9359            indoc! {r#"
 9360                let a = {ˇ
 9361                    key: "value",
 9362                };
 9363            "#},
 9364            cx,
 9365        );
 9366    });
 9367    editor.update_in(cx, |editor, window, cx| {
 9368        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9369    });
 9370    editor.update(cx, |editor, cx| {
 9371        assert_text_with_selections(
 9372            editor,
 9373            indoc! {r#"
 9374                let a = «ˇ{
 9375                    key: "value",
 9376                }»;
 9377            "#},
 9378            cx,
 9379        );
 9380    });
 9381
 9382    // Test case 2: Cursor after ':'
 9383    editor.update_in(cx, |editor, window, cx| {
 9384        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9385            s.select_display_ranges([
 9386                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9387            ]);
 9388        });
 9389    });
 9390    editor.update(cx, |editor, cx| {
 9391        assert_text_with_selections(
 9392            editor,
 9393            indoc! {r#"
 9394                let a = {
 9395                    key:ˇ "value",
 9396                };
 9397            "#},
 9398            cx,
 9399        );
 9400    });
 9401    editor.update_in(cx, |editor, window, cx| {
 9402        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9403    });
 9404    editor.update(cx, |editor, cx| {
 9405        assert_text_with_selections(
 9406            editor,
 9407            indoc! {r#"
 9408                let a = {
 9409                    «ˇkey: "value"»,
 9410                };
 9411            "#},
 9412            cx,
 9413        );
 9414    });
 9415    editor.update_in(cx, |editor, window, cx| {
 9416        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9417    });
 9418    editor.update(cx, |editor, cx| {
 9419        assert_text_with_selections(
 9420            editor,
 9421            indoc! {r#"
 9422                let a = «ˇ{
 9423                    key: "value",
 9424                }»;
 9425            "#},
 9426            cx,
 9427        );
 9428    });
 9429
 9430    // Test case 3: Cursor after ','
 9431    editor.update_in(cx, |editor, window, cx| {
 9432        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9433            s.select_display_ranges([
 9434                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9435            ]);
 9436        });
 9437    });
 9438    editor.update(cx, |editor, cx| {
 9439        assert_text_with_selections(
 9440            editor,
 9441            indoc! {r#"
 9442                let a = {
 9443                    key: "value",ˇ
 9444                };
 9445            "#},
 9446            cx,
 9447        );
 9448    });
 9449    editor.update_in(cx, |editor, window, cx| {
 9450        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9451    });
 9452    editor.update(cx, |editor, cx| {
 9453        assert_text_with_selections(
 9454            editor,
 9455            indoc! {r#"
 9456                let a = «ˇ{
 9457                    key: "value",
 9458                }»;
 9459            "#},
 9460            cx,
 9461        );
 9462    });
 9463
 9464    // Test case 4: Cursor after ';'
 9465    editor.update_in(cx, |editor, window, cx| {
 9466        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9467            s.select_display_ranges([
 9468                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9469            ]);
 9470        });
 9471    });
 9472    editor.update(cx, |editor, cx| {
 9473        assert_text_with_selections(
 9474            editor,
 9475            indoc! {r#"
 9476                let a = {
 9477                    key: "value",
 9478                };ˇ
 9479            "#},
 9480            cx,
 9481        );
 9482    });
 9483    editor.update_in(cx, |editor, window, cx| {
 9484        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9485    });
 9486    editor.update(cx, |editor, cx| {
 9487        assert_text_with_selections(
 9488            editor,
 9489            indoc! {r#"
 9490                «ˇlet a = {
 9491                    key: "value",
 9492                };
 9493                »"#},
 9494            cx,
 9495        );
 9496    });
 9497}
 9498
 9499#[gpui::test]
 9500async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9501    init_test(cx, |_| {});
 9502
 9503    let language = Arc::new(Language::new(
 9504        LanguageConfig::default(),
 9505        Some(tree_sitter_rust::LANGUAGE.into()),
 9506    ));
 9507
 9508    let text = r#"
 9509        use mod1::mod2::{mod3, mod4};
 9510
 9511        fn fn_1(param1: bool, param2: &str) {
 9512            let var1 = "hello world";
 9513        }
 9514    "#
 9515    .unindent();
 9516
 9517    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9518    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9519    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9520
 9521    editor
 9522        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9523        .await;
 9524
 9525    // Test 1: Cursor on a letter of a string word
 9526    editor.update_in(cx, |editor, window, cx| {
 9527        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9528            s.select_display_ranges([
 9529                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9530            ]);
 9531        });
 9532    });
 9533    editor.update_in(cx, |editor, window, cx| {
 9534        assert_text_with_selections(
 9535            editor,
 9536            indoc! {r#"
 9537                use mod1::mod2::{mod3, mod4};
 9538
 9539                fn fn_1(param1: bool, param2: &str) {
 9540                    let var1 = "hˇello world";
 9541                }
 9542            "#},
 9543            cx,
 9544        );
 9545        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9546        assert_text_with_selections(
 9547            editor,
 9548            indoc! {r#"
 9549                use mod1::mod2::{mod3, mod4};
 9550
 9551                fn fn_1(param1: bool, param2: &str) {
 9552                    let var1 = "«ˇhello» world";
 9553                }
 9554            "#},
 9555            cx,
 9556        );
 9557    });
 9558
 9559    // Test 2: Partial selection within a word
 9560    editor.update_in(cx, |editor, window, cx| {
 9561        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9562            s.select_display_ranges([
 9563                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9564            ]);
 9565        });
 9566    });
 9567    editor.update_in(cx, |editor, window, cx| {
 9568        assert_text_with_selections(
 9569            editor,
 9570            indoc! {r#"
 9571                use mod1::mod2::{mod3, mod4};
 9572
 9573                fn fn_1(param1: bool, param2: &str) {
 9574                    let var1 = "h«elˇ»lo world";
 9575                }
 9576            "#},
 9577            cx,
 9578        );
 9579        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9580        assert_text_with_selections(
 9581            editor,
 9582            indoc! {r#"
 9583                use mod1::mod2::{mod3, mod4};
 9584
 9585                fn fn_1(param1: bool, param2: &str) {
 9586                    let var1 = "«ˇhello» world";
 9587                }
 9588            "#},
 9589            cx,
 9590        );
 9591    });
 9592
 9593    // Test 3: Complete word already selected
 9594    editor.update_in(cx, |editor, window, cx| {
 9595        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9596            s.select_display_ranges([
 9597                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9598            ]);
 9599        });
 9600    });
 9601    editor.update_in(cx, |editor, window, cx| {
 9602        assert_text_with_selections(
 9603            editor,
 9604            indoc! {r#"
 9605                use mod1::mod2::{mod3, mod4};
 9606
 9607                fn fn_1(param1: bool, param2: &str) {
 9608                    let var1 = "«helloˇ» world";
 9609                }
 9610            "#},
 9611            cx,
 9612        );
 9613        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9614        assert_text_with_selections(
 9615            editor,
 9616            indoc! {r#"
 9617                use mod1::mod2::{mod3, mod4};
 9618
 9619                fn fn_1(param1: bool, param2: &str) {
 9620                    let var1 = "«hello worldˇ»";
 9621                }
 9622            "#},
 9623            cx,
 9624        );
 9625    });
 9626
 9627    // Test 4: Selection spanning across words
 9628    editor.update_in(cx, |editor, window, cx| {
 9629        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9630            s.select_display_ranges([
 9631                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9632            ]);
 9633        });
 9634    });
 9635    editor.update_in(cx, |editor, window, cx| {
 9636        assert_text_with_selections(
 9637            editor,
 9638            indoc! {r#"
 9639                use mod1::mod2::{mod3, mod4};
 9640
 9641                fn fn_1(param1: bool, param2: &str) {
 9642                    let var1 = "hel«lo woˇ»rld";
 9643                }
 9644            "#},
 9645            cx,
 9646        );
 9647        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9648        assert_text_with_selections(
 9649            editor,
 9650            indoc! {r#"
 9651                use mod1::mod2::{mod3, mod4};
 9652
 9653                fn fn_1(param1: bool, param2: &str) {
 9654                    let var1 = "«ˇhello world»";
 9655                }
 9656            "#},
 9657            cx,
 9658        );
 9659    });
 9660
 9661    // Test 5: Expansion beyond string
 9662    editor.update_in(cx, |editor, window, cx| {
 9663        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9664        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9665        assert_text_with_selections(
 9666            editor,
 9667            indoc! {r#"
 9668                use mod1::mod2::{mod3, mod4};
 9669
 9670                fn fn_1(param1: bool, param2: &str) {
 9671                    «ˇlet var1 = "hello world";»
 9672                }
 9673            "#},
 9674            cx,
 9675        );
 9676    });
 9677}
 9678
 9679#[gpui::test]
 9680async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9681    init_test(cx, |_| {});
 9682
 9683    let mut cx = EditorTestContext::new(cx).await;
 9684
 9685    let language = Arc::new(Language::new(
 9686        LanguageConfig::default(),
 9687        Some(tree_sitter_rust::LANGUAGE.into()),
 9688    ));
 9689
 9690    cx.update_buffer(|buffer, cx| {
 9691        buffer.set_language(Some(language), cx);
 9692    });
 9693
 9694    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9695    cx.update_editor(|editor, window, cx| {
 9696        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9697    });
 9698
 9699    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9700
 9701    cx.set_state(indoc! { r#"fn a() {
 9702          // what
 9703          // a
 9704          // ˇlong
 9705          // method
 9706          // I
 9707          // sure
 9708          // hope
 9709          // it
 9710          // works
 9711    }"# });
 9712
 9713    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9714    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9715    cx.update(|_, cx| {
 9716        multi_buffer.update(cx, |multi_buffer, cx| {
 9717            multi_buffer.set_excerpts_for_path(
 9718                PathKey::for_buffer(&buffer, cx),
 9719                buffer,
 9720                [Point::new(1, 0)..Point::new(1, 0)],
 9721                3,
 9722                cx,
 9723            );
 9724        });
 9725    });
 9726
 9727    let editor2 = cx.new_window_entity(|window, cx| {
 9728        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9729    });
 9730
 9731    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9732    cx.update_editor(|editor, window, cx| {
 9733        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9734            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9735        })
 9736    });
 9737
 9738    cx.assert_editor_state(indoc! { "
 9739        fn a() {
 9740              // what
 9741              // a
 9742        ˇ      // long
 9743              // method"});
 9744
 9745    cx.update_editor(|editor, window, cx| {
 9746        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9747    });
 9748
 9749    // Although we could potentially make the action work when the syntax node
 9750    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9751    // did. Maybe we could also expand the excerpt to contain the range?
 9752    cx.assert_editor_state(indoc! { "
 9753        fn a() {
 9754              // what
 9755              // a
 9756        ˇ      // long
 9757              // method"});
 9758}
 9759
 9760#[gpui::test]
 9761async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9762    init_test(cx, |_| {});
 9763
 9764    let base_text = r#"
 9765        impl A {
 9766            // this is an uncommitted comment
 9767
 9768            fn b() {
 9769                c();
 9770            }
 9771
 9772            // this is another uncommitted comment
 9773
 9774            fn d() {
 9775                // e
 9776                // f
 9777            }
 9778        }
 9779
 9780        fn g() {
 9781            // h
 9782        }
 9783    "#
 9784    .unindent();
 9785
 9786    let text = r#"
 9787        ˇimpl A {
 9788
 9789            fn b() {
 9790                c();
 9791            }
 9792
 9793            fn d() {
 9794                // e
 9795                // f
 9796            }
 9797        }
 9798
 9799        fn g() {
 9800            // h
 9801        }
 9802    "#
 9803    .unindent();
 9804
 9805    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9806    cx.set_state(&text);
 9807    cx.set_head_text(&base_text);
 9808    cx.update_editor(|editor, window, cx| {
 9809        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9810    });
 9811
 9812    cx.assert_state_with_diff(
 9813        "
 9814        ˇimpl A {
 9815      -     // this is an uncommitted comment
 9816
 9817            fn b() {
 9818                c();
 9819            }
 9820
 9821      -     // this is another uncommitted comment
 9822      -
 9823            fn d() {
 9824                // e
 9825                // f
 9826            }
 9827        }
 9828
 9829        fn g() {
 9830            // h
 9831        }
 9832    "
 9833        .unindent(),
 9834    );
 9835
 9836    let expected_display_text = "
 9837        impl A {
 9838            // this is an uncommitted comment
 9839
 9840            fn b() {
 9841 9842            }
 9843
 9844            // this is another uncommitted comment
 9845
 9846            fn d() {
 9847 9848            }
 9849        }
 9850
 9851        fn g() {
 9852 9853        }
 9854        "
 9855    .unindent();
 9856
 9857    cx.update_editor(|editor, window, cx| {
 9858        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9859        assert_eq!(editor.display_text(cx), expected_display_text);
 9860    });
 9861}
 9862
 9863#[gpui::test]
 9864async fn test_autoindent(cx: &mut TestAppContext) {
 9865    init_test(cx, |_| {});
 9866
 9867    let language = Arc::new(
 9868        Language::new(
 9869            LanguageConfig {
 9870                brackets: BracketPairConfig {
 9871                    pairs: vec![
 9872                        BracketPair {
 9873                            start: "{".to_string(),
 9874                            end: "}".to_string(),
 9875                            close: false,
 9876                            surround: false,
 9877                            newline: true,
 9878                        },
 9879                        BracketPair {
 9880                            start: "(".to_string(),
 9881                            end: ")".to_string(),
 9882                            close: false,
 9883                            surround: false,
 9884                            newline: true,
 9885                        },
 9886                    ],
 9887                    ..Default::default()
 9888                },
 9889                ..Default::default()
 9890            },
 9891            Some(tree_sitter_rust::LANGUAGE.into()),
 9892        )
 9893        .with_indents_query(
 9894            r#"
 9895                (_ "(" ")" @end) @indent
 9896                (_ "{" "}" @end) @indent
 9897            "#,
 9898        )
 9899        .unwrap(),
 9900    );
 9901
 9902    let text = "fn a() {}";
 9903
 9904    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9905    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9906    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9907    editor
 9908        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9909        .await;
 9910
 9911    editor.update_in(cx, |editor, window, cx| {
 9912        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9913            s.select_ranges([
 9914                MultiBufferOffset(5)..MultiBufferOffset(5),
 9915                MultiBufferOffset(8)..MultiBufferOffset(8),
 9916                MultiBufferOffset(9)..MultiBufferOffset(9),
 9917            ])
 9918        });
 9919        editor.newline(&Newline, window, cx);
 9920        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9921        assert_eq!(
 9922            editor.selections.ranges(&editor.display_snapshot(cx)),
 9923            &[
 9924                Point::new(1, 4)..Point::new(1, 4),
 9925                Point::new(3, 4)..Point::new(3, 4),
 9926                Point::new(5, 0)..Point::new(5, 0)
 9927            ]
 9928        );
 9929    });
 9930}
 9931
 9932#[gpui::test]
 9933async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9934    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9935
 9936    let language = Arc::new(
 9937        Language::new(
 9938            LanguageConfig {
 9939                brackets: BracketPairConfig {
 9940                    pairs: vec![
 9941                        BracketPair {
 9942                            start: "{".to_string(),
 9943                            end: "}".to_string(),
 9944                            close: false,
 9945                            surround: false,
 9946                            newline: true,
 9947                        },
 9948                        BracketPair {
 9949                            start: "(".to_string(),
 9950                            end: ")".to_string(),
 9951                            close: false,
 9952                            surround: false,
 9953                            newline: true,
 9954                        },
 9955                    ],
 9956                    ..Default::default()
 9957                },
 9958                ..Default::default()
 9959            },
 9960            Some(tree_sitter_rust::LANGUAGE.into()),
 9961        )
 9962        .with_indents_query(
 9963            r#"
 9964                (_ "(" ")" @end) @indent
 9965                (_ "{" "}" @end) @indent
 9966            "#,
 9967        )
 9968        .unwrap(),
 9969    );
 9970
 9971    let text = "fn a() {}";
 9972
 9973    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9974    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9975    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9976    editor
 9977        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9978        .await;
 9979
 9980    editor.update_in(cx, |editor, window, cx| {
 9981        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9982            s.select_ranges([
 9983                MultiBufferOffset(5)..MultiBufferOffset(5),
 9984                MultiBufferOffset(8)..MultiBufferOffset(8),
 9985                MultiBufferOffset(9)..MultiBufferOffset(9),
 9986            ])
 9987        });
 9988        editor.newline(&Newline, window, cx);
 9989        assert_eq!(
 9990            editor.text(cx),
 9991            indoc!(
 9992                "
 9993                fn a(
 9994
 9995                ) {
 9996
 9997                }
 9998                "
 9999            )
10000        );
10001        assert_eq!(
10002            editor.selections.ranges(&editor.display_snapshot(cx)),
10003            &[
10004                Point::new(1, 0)..Point::new(1, 0),
10005                Point::new(3, 0)..Point::new(3, 0),
10006                Point::new(5, 0)..Point::new(5, 0)
10007            ]
10008        );
10009    });
10010}
10011
10012#[gpui::test]
10013async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10014    init_test(cx, |settings| {
10015        settings.defaults.auto_indent = Some(true);
10016        settings.languages.0.insert(
10017            "python".into(),
10018            LanguageSettingsContent {
10019                auto_indent: Some(false),
10020                ..Default::default()
10021            },
10022        );
10023    });
10024
10025    let mut cx = EditorTestContext::new(cx).await;
10026
10027    let injected_language = Arc::new(
10028        Language::new(
10029            LanguageConfig {
10030                brackets: BracketPairConfig {
10031                    pairs: vec![
10032                        BracketPair {
10033                            start: "{".to_string(),
10034                            end: "}".to_string(),
10035                            close: false,
10036                            surround: false,
10037                            newline: true,
10038                        },
10039                        BracketPair {
10040                            start: "(".to_string(),
10041                            end: ")".to_string(),
10042                            close: true,
10043                            surround: false,
10044                            newline: true,
10045                        },
10046                    ],
10047                    ..Default::default()
10048                },
10049                name: "python".into(),
10050                ..Default::default()
10051            },
10052            Some(tree_sitter_python::LANGUAGE.into()),
10053        )
10054        .with_indents_query(
10055            r#"
10056                (_ "(" ")" @end) @indent
10057                (_ "{" "}" @end) @indent
10058            "#,
10059        )
10060        .unwrap(),
10061    );
10062
10063    let language = Arc::new(
10064        Language::new(
10065            LanguageConfig {
10066                brackets: BracketPairConfig {
10067                    pairs: vec![
10068                        BracketPair {
10069                            start: "{".to_string(),
10070                            end: "}".to_string(),
10071                            close: false,
10072                            surround: false,
10073                            newline: true,
10074                        },
10075                        BracketPair {
10076                            start: "(".to_string(),
10077                            end: ")".to_string(),
10078                            close: true,
10079                            surround: false,
10080                            newline: true,
10081                        },
10082                    ],
10083                    ..Default::default()
10084                },
10085                name: LanguageName::new_static("rust"),
10086                ..Default::default()
10087            },
10088            Some(tree_sitter_rust::LANGUAGE.into()),
10089        )
10090        .with_indents_query(
10091            r#"
10092                (_ "(" ")" @end) @indent
10093                (_ "{" "}" @end) @indent
10094            "#,
10095        )
10096        .unwrap()
10097        .with_injection_query(
10098            r#"
10099            (macro_invocation
10100                macro: (identifier) @_macro_name
10101                (token_tree) @injection.content
10102                (#set! injection.language "python"))
10103           "#,
10104        )
10105        .unwrap(),
10106    );
10107
10108    cx.language_registry().add(injected_language);
10109    cx.language_registry().add(language.clone());
10110
10111    cx.update_buffer(|buffer, cx| {
10112        buffer.set_language(Some(language), cx);
10113    });
10114
10115    cx.set_state(r#"struct A {ˇ}"#);
10116
10117    cx.update_editor(|editor, window, cx| {
10118        editor.newline(&Default::default(), window, cx);
10119    });
10120
10121    cx.assert_editor_state(indoc!(
10122        "struct A {
10123            ˇ
10124        }"
10125    ));
10126
10127    cx.set_state(r#"select_biased!(ˇ)"#);
10128
10129    cx.update_editor(|editor, window, cx| {
10130        editor.newline(&Default::default(), window, cx);
10131        editor.handle_input("def ", window, cx);
10132        editor.handle_input("(", window, cx);
10133        editor.newline(&Default::default(), window, cx);
10134        editor.handle_input("a", window, cx);
10135    });
10136
10137    cx.assert_editor_state(indoc!(
10138        "select_biased!(
10139        def (
1014010141        )
10142        )"
10143    ));
10144}
10145
10146#[gpui::test]
10147async fn test_autoindent_selections(cx: &mut TestAppContext) {
10148    init_test(cx, |_| {});
10149
10150    {
10151        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10152        cx.set_state(indoc! {"
10153            impl A {
10154
10155                fn b() {}
10156
10157            «fn c() {
10158
10159            }ˇ»
10160            }
10161        "});
10162
10163        cx.update_editor(|editor, window, cx| {
10164            editor.autoindent(&Default::default(), window, cx);
10165        });
10166
10167        cx.assert_editor_state(indoc! {"
10168            impl A {
10169
10170                fn b() {}
10171
10172                «fn c() {
10173
10174                }ˇ»
10175            }
10176        "});
10177    }
10178
10179    {
10180        let mut cx = EditorTestContext::new_multibuffer(
10181            cx,
10182            [indoc! { "
10183                impl A {
10184                «
10185                // a
10186                fn b(){}
10187                »
10188                «
10189                    }
10190                    fn c(){}
10191                »
10192            "}],
10193        );
10194
10195        let buffer = cx.update_editor(|editor, _, cx| {
10196            let buffer = editor.buffer().update(cx, |buffer, _| {
10197                buffer.all_buffers().iter().next().unwrap().clone()
10198            });
10199            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10200            buffer
10201        });
10202
10203        cx.run_until_parked();
10204        cx.update_editor(|editor, window, cx| {
10205            editor.select_all(&Default::default(), window, cx);
10206            editor.autoindent(&Default::default(), window, cx)
10207        });
10208        cx.run_until_parked();
10209
10210        cx.update(|_, cx| {
10211            assert_eq!(
10212                buffer.read(cx).text(),
10213                indoc! { "
10214                    impl A {
10215
10216                        // a
10217                        fn b(){}
10218
10219
10220                    }
10221                    fn c(){}
10222
10223                " }
10224            )
10225        });
10226    }
10227}
10228
10229#[gpui::test]
10230async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10231    init_test(cx, |_| {});
10232
10233    let mut cx = EditorTestContext::new(cx).await;
10234
10235    let language = Arc::new(Language::new(
10236        LanguageConfig {
10237            brackets: BracketPairConfig {
10238                pairs: vec![
10239                    BracketPair {
10240                        start: "{".to_string(),
10241                        end: "}".to_string(),
10242                        close: true,
10243                        surround: true,
10244                        newline: true,
10245                    },
10246                    BracketPair {
10247                        start: "(".to_string(),
10248                        end: ")".to_string(),
10249                        close: true,
10250                        surround: true,
10251                        newline: true,
10252                    },
10253                    BracketPair {
10254                        start: "/*".to_string(),
10255                        end: " */".to_string(),
10256                        close: true,
10257                        surround: true,
10258                        newline: true,
10259                    },
10260                    BracketPair {
10261                        start: "[".to_string(),
10262                        end: "]".to_string(),
10263                        close: false,
10264                        surround: false,
10265                        newline: true,
10266                    },
10267                    BracketPair {
10268                        start: "\"".to_string(),
10269                        end: "\"".to_string(),
10270                        close: true,
10271                        surround: true,
10272                        newline: false,
10273                    },
10274                    BracketPair {
10275                        start: "<".to_string(),
10276                        end: ">".to_string(),
10277                        close: false,
10278                        surround: true,
10279                        newline: true,
10280                    },
10281                ],
10282                ..Default::default()
10283            },
10284            autoclose_before: "})]".to_string(),
10285            ..Default::default()
10286        },
10287        Some(tree_sitter_rust::LANGUAGE.into()),
10288    ));
10289
10290    cx.language_registry().add(language.clone());
10291    cx.update_buffer(|buffer, cx| {
10292        buffer.set_language(Some(language), cx);
10293    });
10294
10295    cx.set_state(
10296        &r#"
10297            🏀ˇ
10298            εˇ
10299            ❤️ˇ
10300        "#
10301        .unindent(),
10302    );
10303
10304    // autoclose multiple nested brackets at multiple cursors
10305    cx.update_editor(|editor, window, cx| {
10306        editor.handle_input("{", window, cx);
10307        editor.handle_input("{", window, cx);
10308        editor.handle_input("{", window, cx);
10309    });
10310    cx.assert_editor_state(
10311        &"
10312            🏀{{{ˇ}}}
10313            ε{{{ˇ}}}
10314            ❤️{{{ˇ}}}
10315        "
10316        .unindent(),
10317    );
10318
10319    // insert a different closing bracket
10320    cx.update_editor(|editor, window, cx| {
10321        editor.handle_input(")", window, cx);
10322    });
10323    cx.assert_editor_state(
10324        &"
10325            🏀{{{)ˇ}}}
10326            ε{{{)ˇ}}}
10327            ❤️{{{)ˇ}}}
10328        "
10329        .unindent(),
10330    );
10331
10332    // skip over the auto-closed brackets when typing a closing bracket
10333    cx.update_editor(|editor, window, cx| {
10334        editor.move_right(&MoveRight, window, cx);
10335        editor.handle_input("}", window, cx);
10336        editor.handle_input("}", window, cx);
10337        editor.handle_input("}", window, cx);
10338    });
10339    cx.assert_editor_state(
10340        &"
10341            🏀{{{)}}}}ˇ
10342            ε{{{)}}}}ˇ
10343            ❤️{{{)}}}}ˇ
10344        "
10345        .unindent(),
10346    );
10347
10348    // autoclose multi-character pairs
10349    cx.set_state(
10350        &"
10351            ˇ
10352            ˇ
10353        "
10354        .unindent(),
10355    );
10356    cx.update_editor(|editor, window, cx| {
10357        editor.handle_input("/", window, cx);
10358        editor.handle_input("*", window, cx);
10359    });
10360    cx.assert_editor_state(
10361        &"
10362            /*ˇ */
10363            /*ˇ */
10364        "
10365        .unindent(),
10366    );
10367
10368    // one cursor autocloses a multi-character pair, one cursor
10369    // does not autoclose.
10370    cx.set_state(
10371        &"
1037210373            ˇ
10374        "
10375        .unindent(),
10376    );
10377    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10378    cx.assert_editor_state(
10379        &"
10380            /*ˇ */
1038110382        "
10383        .unindent(),
10384    );
10385
10386    // Don't autoclose if the next character isn't whitespace and isn't
10387    // listed in the language's "autoclose_before" section.
10388    cx.set_state("ˇa b");
10389    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10390    cx.assert_editor_state("{ˇa b");
10391
10392    // Don't autoclose if `close` is false for the bracket pair
10393    cx.set_state("ˇ");
10394    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10395    cx.assert_editor_state("");
10396
10397    // Surround with brackets if text is selected
10398    cx.set_state("«aˇ» b");
10399    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10400    cx.assert_editor_state("{«aˇ»} b");
10401
10402    // Autoclose when not immediately after a word character
10403    cx.set_state("a ˇ");
10404    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10405    cx.assert_editor_state("a \"ˇ\"");
10406
10407    // Autoclose pair where the start and end characters are the same
10408    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10409    cx.assert_editor_state("a \"\"ˇ");
10410
10411    // Don't autoclose when immediately after a word character
10412    cx.set_state("");
10413    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10414    cx.assert_editor_state("a\"ˇ");
10415
10416    // Do autoclose when after a non-word character
10417    cx.set_state("");
10418    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10419    cx.assert_editor_state("{\"ˇ\"");
10420
10421    // Non identical pairs autoclose regardless of preceding character
10422    cx.set_state("");
10423    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10424    cx.assert_editor_state("a{ˇ}");
10425
10426    // Don't autoclose pair if autoclose is disabled
10427    cx.set_state("ˇ");
10428    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10429    cx.assert_editor_state("");
10430
10431    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10432    cx.set_state("«aˇ» b");
10433    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10434    cx.assert_editor_state("<«aˇ»> b");
10435}
10436
10437#[gpui::test]
10438async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10439    init_test(cx, |settings| {
10440        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10441    });
10442
10443    let mut cx = EditorTestContext::new(cx).await;
10444
10445    let language = Arc::new(Language::new(
10446        LanguageConfig {
10447            brackets: BracketPairConfig {
10448                pairs: vec![
10449                    BracketPair {
10450                        start: "{".to_string(),
10451                        end: "}".to_string(),
10452                        close: true,
10453                        surround: true,
10454                        newline: true,
10455                    },
10456                    BracketPair {
10457                        start: "(".to_string(),
10458                        end: ")".to_string(),
10459                        close: true,
10460                        surround: true,
10461                        newline: true,
10462                    },
10463                    BracketPair {
10464                        start: "[".to_string(),
10465                        end: "]".to_string(),
10466                        close: false,
10467                        surround: false,
10468                        newline: true,
10469                    },
10470                ],
10471                ..Default::default()
10472            },
10473            autoclose_before: "})]".to_string(),
10474            ..Default::default()
10475        },
10476        Some(tree_sitter_rust::LANGUAGE.into()),
10477    ));
10478
10479    cx.language_registry().add(language.clone());
10480    cx.update_buffer(|buffer, cx| {
10481        buffer.set_language(Some(language), cx);
10482    });
10483
10484    cx.set_state(
10485        &"
10486            ˇ
10487            ˇ
10488            ˇ
10489        "
10490        .unindent(),
10491    );
10492
10493    // ensure only matching closing brackets are skipped over
10494    cx.update_editor(|editor, window, cx| {
10495        editor.handle_input("}", window, cx);
10496        editor.move_left(&MoveLeft, window, cx);
10497        editor.handle_input(")", window, cx);
10498        editor.move_left(&MoveLeft, window, cx);
10499    });
10500    cx.assert_editor_state(
10501        &"
10502            ˇ)}
10503            ˇ)}
10504            ˇ)}
10505        "
10506        .unindent(),
10507    );
10508
10509    // skip-over closing brackets at multiple cursors
10510    cx.update_editor(|editor, window, cx| {
10511        editor.handle_input(")", window, cx);
10512        editor.handle_input("}", window, cx);
10513    });
10514    cx.assert_editor_state(
10515        &"
10516            )}ˇ
10517            )}ˇ
10518            )}ˇ
10519        "
10520        .unindent(),
10521    );
10522
10523    // ignore non-close brackets
10524    cx.update_editor(|editor, window, cx| {
10525        editor.handle_input("]", window, cx);
10526        editor.move_left(&MoveLeft, window, cx);
10527        editor.handle_input("]", window, cx);
10528    });
10529    cx.assert_editor_state(
10530        &"
10531            )}]ˇ]
10532            )}]ˇ]
10533            )}]ˇ]
10534        "
10535        .unindent(),
10536    );
10537}
10538
10539#[gpui::test]
10540async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10541    init_test(cx, |_| {});
10542
10543    let mut cx = EditorTestContext::new(cx).await;
10544
10545    let html_language = Arc::new(
10546        Language::new(
10547            LanguageConfig {
10548                name: "HTML".into(),
10549                brackets: BracketPairConfig {
10550                    pairs: vec![
10551                        BracketPair {
10552                            start: "<".into(),
10553                            end: ">".into(),
10554                            close: true,
10555                            ..Default::default()
10556                        },
10557                        BracketPair {
10558                            start: "{".into(),
10559                            end: "}".into(),
10560                            close: true,
10561                            ..Default::default()
10562                        },
10563                        BracketPair {
10564                            start: "(".into(),
10565                            end: ")".into(),
10566                            close: true,
10567                            ..Default::default()
10568                        },
10569                    ],
10570                    ..Default::default()
10571                },
10572                autoclose_before: "})]>".into(),
10573                ..Default::default()
10574            },
10575            Some(tree_sitter_html::LANGUAGE.into()),
10576        )
10577        .with_injection_query(
10578            r#"
10579            (script_element
10580                (raw_text) @injection.content
10581                (#set! injection.language "javascript"))
10582            "#,
10583        )
10584        .unwrap(),
10585    );
10586
10587    let javascript_language = Arc::new(Language::new(
10588        LanguageConfig {
10589            name: "JavaScript".into(),
10590            brackets: BracketPairConfig {
10591                pairs: vec![
10592                    BracketPair {
10593                        start: "/*".into(),
10594                        end: " */".into(),
10595                        close: true,
10596                        ..Default::default()
10597                    },
10598                    BracketPair {
10599                        start: "{".into(),
10600                        end: "}".into(),
10601                        close: true,
10602                        ..Default::default()
10603                    },
10604                    BracketPair {
10605                        start: "(".into(),
10606                        end: ")".into(),
10607                        close: true,
10608                        ..Default::default()
10609                    },
10610                ],
10611                ..Default::default()
10612            },
10613            autoclose_before: "})]>".into(),
10614            ..Default::default()
10615        },
10616        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10617    ));
10618
10619    cx.language_registry().add(html_language.clone());
10620    cx.language_registry().add(javascript_language);
10621    cx.executor().run_until_parked();
10622
10623    cx.update_buffer(|buffer, cx| {
10624        buffer.set_language(Some(html_language), cx);
10625    });
10626
10627    cx.set_state(
10628        &r#"
10629            <body>ˇ
10630                <script>
10631                    var x = 1;ˇ
10632                </script>
10633            </body>ˇ
10634        "#
10635        .unindent(),
10636    );
10637
10638    // Precondition: different languages are active at different locations.
10639    cx.update_editor(|editor, window, cx| {
10640        let snapshot = editor.snapshot(window, cx);
10641        let cursors = editor
10642            .selections
10643            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10644        let languages = cursors
10645            .iter()
10646            .map(|c| snapshot.language_at(c.start).unwrap().name())
10647            .collect::<Vec<_>>();
10648        assert_eq!(
10649            languages,
10650            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10651        );
10652    });
10653
10654    // Angle brackets autoclose in HTML, but not JavaScript.
10655    cx.update_editor(|editor, window, cx| {
10656        editor.handle_input("<", window, cx);
10657        editor.handle_input("a", window, cx);
10658    });
10659    cx.assert_editor_state(
10660        &r#"
10661            <body><aˇ>
10662                <script>
10663                    var x = 1;<aˇ
10664                </script>
10665            </body><aˇ>
10666        "#
10667        .unindent(),
10668    );
10669
10670    // Curly braces and parens autoclose in both HTML and JavaScript.
10671    cx.update_editor(|editor, window, cx| {
10672        editor.handle_input(" b=", window, cx);
10673        editor.handle_input("{", window, cx);
10674        editor.handle_input("c", window, cx);
10675        editor.handle_input("(", window, cx);
10676    });
10677    cx.assert_editor_state(
10678        &r#"
10679            <body><a b={c(ˇ)}>
10680                <script>
10681                    var x = 1;<a b={c(ˇ)}
10682                </script>
10683            </body><a b={c(ˇ)}>
10684        "#
10685        .unindent(),
10686    );
10687
10688    // Brackets that were already autoclosed are skipped.
10689    cx.update_editor(|editor, window, cx| {
10690        editor.handle_input(")", window, cx);
10691        editor.handle_input("d", window, cx);
10692        editor.handle_input("}", window, cx);
10693    });
10694    cx.assert_editor_state(
10695        &r#"
10696            <body><a b={c()d}ˇ>
10697                <script>
10698                    var x = 1;<a b={c()d}ˇ
10699                </script>
10700            </body><a b={c()d}ˇ>
10701        "#
10702        .unindent(),
10703    );
10704    cx.update_editor(|editor, window, cx| {
10705        editor.handle_input(">", window, cx);
10706    });
10707    cx.assert_editor_state(
10708        &r#"
10709            <body><a b={c()d}>ˇ
10710                <script>
10711                    var x = 1;<a b={c()d}>ˇ
10712                </script>
10713            </body><a b={c()d}>ˇ
10714        "#
10715        .unindent(),
10716    );
10717
10718    // Reset
10719    cx.set_state(
10720        &r#"
10721            <body>ˇ
10722                <script>
10723                    var x = 1;ˇ
10724                </script>
10725            </body>ˇ
10726        "#
10727        .unindent(),
10728    );
10729
10730    cx.update_editor(|editor, window, cx| {
10731        editor.handle_input("<", window, cx);
10732    });
10733    cx.assert_editor_state(
10734        &r#"
10735            <body><ˇ>
10736                <script>
10737                    var x = 1;<ˇ
10738                </script>
10739            </body><ˇ>
10740        "#
10741        .unindent(),
10742    );
10743
10744    // When backspacing, the closing angle brackets are removed.
10745    cx.update_editor(|editor, window, cx| {
10746        editor.backspace(&Backspace, window, cx);
10747    });
10748    cx.assert_editor_state(
10749        &r#"
10750            <body>ˇ
10751                <script>
10752                    var x = 1;ˇ
10753                </script>
10754            </body>ˇ
10755        "#
10756        .unindent(),
10757    );
10758
10759    // Block comments autoclose in JavaScript, but not HTML.
10760    cx.update_editor(|editor, window, cx| {
10761        editor.handle_input("/", window, cx);
10762        editor.handle_input("*", window, cx);
10763    });
10764    cx.assert_editor_state(
10765        &r#"
10766            <body>/*ˇ
10767                <script>
10768                    var x = 1;/*ˇ */
10769                </script>
10770            </body>/*ˇ
10771        "#
10772        .unindent(),
10773    );
10774}
10775
10776#[gpui::test]
10777async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10778    init_test(cx, |_| {});
10779
10780    let mut cx = EditorTestContext::new(cx).await;
10781
10782    let rust_language = Arc::new(
10783        Language::new(
10784            LanguageConfig {
10785                name: "Rust".into(),
10786                brackets: serde_json::from_value(json!([
10787                    { "start": "{", "end": "}", "close": true, "newline": true },
10788                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10789                ]))
10790                .unwrap(),
10791                autoclose_before: "})]>".into(),
10792                ..Default::default()
10793            },
10794            Some(tree_sitter_rust::LANGUAGE.into()),
10795        )
10796        .with_override_query("(string_literal) @string")
10797        .unwrap(),
10798    );
10799
10800    cx.language_registry().add(rust_language.clone());
10801    cx.update_buffer(|buffer, cx| {
10802        buffer.set_language(Some(rust_language), cx);
10803    });
10804
10805    cx.set_state(
10806        &r#"
10807            let x = ˇ
10808        "#
10809        .unindent(),
10810    );
10811
10812    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10813    cx.update_editor(|editor, window, cx| {
10814        editor.handle_input("\"", window, cx);
10815    });
10816    cx.assert_editor_state(
10817        &r#"
10818            let x = "ˇ"
10819        "#
10820        .unindent(),
10821    );
10822
10823    // Inserting another quotation mark. The cursor moves across the existing
10824    // automatically-inserted quotation mark.
10825    cx.update_editor(|editor, window, cx| {
10826        editor.handle_input("\"", window, cx);
10827    });
10828    cx.assert_editor_state(
10829        &r#"
10830            let x = ""ˇ
10831        "#
10832        .unindent(),
10833    );
10834
10835    // Reset
10836    cx.set_state(
10837        &r#"
10838            let x = ˇ
10839        "#
10840        .unindent(),
10841    );
10842
10843    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10844    cx.update_editor(|editor, window, cx| {
10845        editor.handle_input("\"", window, cx);
10846        editor.handle_input(" ", window, cx);
10847        editor.move_left(&Default::default(), window, cx);
10848        editor.handle_input("\\", window, cx);
10849        editor.handle_input("\"", window, cx);
10850    });
10851    cx.assert_editor_state(
10852        &r#"
10853            let x = "\"ˇ "
10854        "#
10855        .unindent(),
10856    );
10857
10858    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10859    // mark. Nothing is inserted.
10860    cx.update_editor(|editor, window, cx| {
10861        editor.move_right(&Default::default(), window, cx);
10862        editor.handle_input("\"", window, cx);
10863    });
10864    cx.assert_editor_state(
10865        &r#"
10866            let x = "\" "ˇ
10867        "#
10868        .unindent(),
10869    );
10870}
10871
10872#[gpui::test]
10873async fn test_autoclose_quotes_with_scope_awareness(cx: &mut TestAppContext) {
10874    init_test(cx, |_| {});
10875
10876    let mut cx = EditorTestContext::new(cx).await;
10877    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
10878
10879    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10880
10881    // Double quote inside single-quoted string
10882    cx.set_state(indoc! {r#"
10883        def main():
10884            items = ['"', ˇ]
10885    "#});
10886    cx.update_editor(|editor, window, cx| {
10887        editor.handle_input("\"", window, cx);
10888    });
10889    cx.assert_editor_state(indoc! {r#"
10890        def main():
10891            items = ['"', "ˇ"]
10892    "#});
10893
10894    // Two double quotes inside single-quoted string
10895    cx.set_state(indoc! {r#"
10896        def main():
10897            items = ['""', ˇ]
10898    "#});
10899    cx.update_editor(|editor, window, cx| {
10900        editor.handle_input("\"", window, cx);
10901    });
10902    cx.assert_editor_state(indoc! {r#"
10903        def main():
10904            items = ['""', "ˇ"]
10905    "#});
10906
10907    // Single quote inside double-quoted string
10908    cx.set_state(indoc! {r#"
10909        def main():
10910            items = ["'", ˇ]
10911    "#});
10912    cx.update_editor(|editor, window, cx| {
10913        editor.handle_input("'", window, cx);
10914    });
10915    cx.assert_editor_state(indoc! {r#"
10916        def main():
10917            items = ["'", 'ˇ']
10918    "#});
10919
10920    // Two single quotes inside double-quoted string
10921    cx.set_state(indoc! {r#"
10922        def main():
10923            items = ["''", ˇ]
10924    "#});
10925    cx.update_editor(|editor, window, cx| {
10926        editor.handle_input("'", window, cx);
10927    });
10928    cx.assert_editor_state(indoc! {r#"
10929        def main():
10930            items = ["''", 'ˇ']
10931    "#});
10932
10933    // Mixed quotes on same line
10934    cx.set_state(indoc! {r#"
10935        def main():
10936            items = ['"""', "'''''", ˇ]
10937    "#});
10938    cx.update_editor(|editor, window, cx| {
10939        editor.handle_input("\"", window, cx);
10940    });
10941    cx.assert_editor_state(indoc! {r#"
10942        def main():
10943            items = ['"""', "'''''", "ˇ"]
10944    "#});
10945    cx.update_editor(|editor, window, cx| {
10946        editor.move_right(&MoveRight, window, cx);
10947    });
10948    cx.update_editor(|editor, window, cx| {
10949        editor.handle_input(", ", window, cx);
10950    });
10951    cx.update_editor(|editor, window, cx| {
10952        editor.handle_input("'", window, cx);
10953    });
10954    cx.assert_editor_state(indoc! {r#"
10955        def main():
10956            items = ['"""', "'''''", "", 'ˇ']
10957    "#});
10958}
10959
10960#[gpui::test]
10961async fn test_autoclose_quotes_with_multibyte_characters(cx: &mut TestAppContext) {
10962    init_test(cx, |_| {});
10963
10964    let mut cx = EditorTestContext::new(cx).await;
10965    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
10966    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
10967
10968    cx.set_state(indoc! {r#"
10969        def main():
10970            items = ["🎉", ˇ]
10971    "#});
10972    cx.update_editor(|editor, window, cx| {
10973        editor.handle_input("\"", window, cx);
10974    });
10975    cx.assert_editor_state(indoc! {r#"
10976        def main():
10977            items = ["🎉", "ˇ"]
10978    "#});
10979}
10980
10981#[gpui::test]
10982async fn test_surround_with_pair(cx: &mut TestAppContext) {
10983    init_test(cx, |_| {});
10984
10985    let language = Arc::new(Language::new(
10986        LanguageConfig {
10987            brackets: BracketPairConfig {
10988                pairs: vec![
10989                    BracketPair {
10990                        start: "{".to_string(),
10991                        end: "}".to_string(),
10992                        close: true,
10993                        surround: true,
10994                        newline: true,
10995                    },
10996                    BracketPair {
10997                        start: "/* ".to_string(),
10998                        end: "*/".to_string(),
10999                        close: true,
11000                        surround: true,
11001                        ..Default::default()
11002                    },
11003                ],
11004                ..Default::default()
11005            },
11006            ..Default::default()
11007        },
11008        Some(tree_sitter_rust::LANGUAGE.into()),
11009    ));
11010
11011    let text = r#"
11012        a
11013        b
11014        c
11015    "#
11016    .unindent();
11017
11018    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11019    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11020    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11021    editor
11022        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11023        .await;
11024
11025    editor.update_in(cx, |editor, window, cx| {
11026        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11027            s.select_display_ranges([
11028                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11029                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11030                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
11031            ])
11032        });
11033
11034        editor.handle_input("{", window, cx);
11035        editor.handle_input("{", window, cx);
11036        editor.handle_input("{", window, cx);
11037        assert_eq!(
11038            editor.text(cx),
11039            "
11040                {{{a}}}
11041                {{{b}}}
11042                {{{c}}}
11043            "
11044            .unindent()
11045        );
11046        assert_eq!(
11047            display_ranges(editor, cx),
11048            [
11049                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
11050                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
11051                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
11052            ]
11053        );
11054
11055        editor.undo(&Undo, window, cx);
11056        editor.undo(&Undo, window, cx);
11057        editor.undo(&Undo, window, cx);
11058        assert_eq!(
11059            editor.text(cx),
11060            "
11061                a
11062                b
11063                c
11064            "
11065            .unindent()
11066        );
11067        assert_eq!(
11068            display_ranges(editor, cx),
11069            [
11070                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11071                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11072                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11073            ]
11074        );
11075
11076        // Ensure inserting the first character of a multi-byte bracket pair
11077        // doesn't surround the selections with the bracket.
11078        editor.handle_input("/", window, cx);
11079        assert_eq!(
11080            editor.text(cx),
11081            "
11082                /
11083                /
11084                /
11085            "
11086            .unindent()
11087        );
11088        assert_eq!(
11089            display_ranges(editor, cx),
11090            [
11091                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11092                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11093                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11094            ]
11095        );
11096
11097        editor.undo(&Undo, window, cx);
11098        assert_eq!(
11099            editor.text(cx),
11100            "
11101                a
11102                b
11103                c
11104            "
11105            .unindent()
11106        );
11107        assert_eq!(
11108            display_ranges(editor, cx),
11109            [
11110                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
11111                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
11112                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
11113            ]
11114        );
11115
11116        // Ensure inserting the last character of a multi-byte bracket pair
11117        // doesn't surround the selections with the bracket.
11118        editor.handle_input("*", window, cx);
11119        assert_eq!(
11120            editor.text(cx),
11121            "
11122                *
11123                *
11124                *
11125            "
11126            .unindent()
11127        );
11128        assert_eq!(
11129            display_ranges(editor, cx),
11130            [
11131                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11132                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11133                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11134            ]
11135        );
11136    });
11137}
11138
11139#[gpui::test]
11140async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11141    init_test(cx, |_| {});
11142
11143    let language = Arc::new(Language::new(
11144        LanguageConfig {
11145            brackets: BracketPairConfig {
11146                pairs: vec![BracketPair {
11147                    start: "{".to_string(),
11148                    end: "}".to_string(),
11149                    close: true,
11150                    surround: true,
11151                    newline: true,
11152                }],
11153                ..Default::default()
11154            },
11155            autoclose_before: "}".to_string(),
11156            ..Default::default()
11157        },
11158        Some(tree_sitter_rust::LANGUAGE.into()),
11159    ));
11160
11161    let text = r#"
11162        a
11163        b
11164        c
11165    "#
11166    .unindent();
11167
11168    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11169    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11170    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11171    editor
11172        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11173        .await;
11174
11175    editor.update_in(cx, |editor, window, cx| {
11176        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11177            s.select_ranges([
11178                Point::new(0, 1)..Point::new(0, 1),
11179                Point::new(1, 1)..Point::new(1, 1),
11180                Point::new(2, 1)..Point::new(2, 1),
11181            ])
11182        });
11183
11184        editor.handle_input("{", window, cx);
11185        editor.handle_input("{", window, cx);
11186        editor.handle_input("_", window, cx);
11187        assert_eq!(
11188            editor.text(cx),
11189            "
11190                a{{_}}
11191                b{{_}}
11192                c{{_}}
11193            "
11194            .unindent()
11195        );
11196        assert_eq!(
11197            editor
11198                .selections
11199                .ranges::<Point>(&editor.display_snapshot(cx)),
11200            [
11201                Point::new(0, 4)..Point::new(0, 4),
11202                Point::new(1, 4)..Point::new(1, 4),
11203                Point::new(2, 4)..Point::new(2, 4)
11204            ]
11205        );
11206
11207        editor.backspace(&Default::default(), window, cx);
11208        editor.backspace(&Default::default(), window, cx);
11209        assert_eq!(
11210            editor.text(cx),
11211            "
11212                a{}
11213                b{}
11214                c{}
11215            "
11216            .unindent()
11217        );
11218        assert_eq!(
11219            editor
11220                .selections
11221                .ranges::<Point>(&editor.display_snapshot(cx)),
11222            [
11223                Point::new(0, 2)..Point::new(0, 2),
11224                Point::new(1, 2)..Point::new(1, 2),
11225                Point::new(2, 2)..Point::new(2, 2)
11226            ]
11227        );
11228
11229        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11230        assert_eq!(
11231            editor.text(cx),
11232            "
11233                a
11234                b
11235                c
11236            "
11237            .unindent()
11238        );
11239        assert_eq!(
11240            editor
11241                .selections
11242                .ranges::<Point>(&editor.display_snapshot(cx)),
11243            [
11244                Point::new(0, 1)..Point::new(0, 1),
11245                Point::new(1, 1)..Point::new(1, 1),
11246                Point::new(2, 1)..Point::new(2, 1)
11247            ]
11248        );
11249    });
11250}
11251
11252#[gpui::test]
11253async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11254    init_test(cx, |settings| {
11255        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11256    });
11257
11258    let mut cx = EditorTestContext::new(cx).await;
11259
11260    let language = Arc::new(Language::new(
11261        LanguageConfig {
11262            brackets: BracketPairConfig {
11263                pairs: vec![
11264                    BracketPair {
11265                        start: "{".to_string(),
11266                        end: "}".to_string(),
11267                        close: true,
11268                        surround: true,
11269                        newline: true,
11270                    },
11271                    BracketPair {
11272                        start: "(".to_string(),
11273                        end: ")".to_string(),
11274                        close: true,
11275                        surround: true,
11276                        newline: true,
11277                    },
11278                    BracketPair {
11279                        start: "[".to_string(),
11280                        end: "]".to_string(),
11281                        close: false,
11282                        surround: true,
11283                        newline: true,
11284                    },
11285                ],
11286                ..Default::default()
11287            },
11288            autoclose_before: "})]".to_string(),
11289            ..Default::default()
11290        },
11291        Some(tree_sitter_rust::LANGUAGE.into()),
11292    ));
11293
11294    cx.language_registry().add(language.clone());
11295    cx.update_buffer(|buffer, cx| {
11296        buffer.set_language(Some(language), cx);
11297    });
11298
11299    cx.set_state(
11300        &"
11301            {(ˇ)}
11302            [[ˇ]]
11303            {(ˇ)}
11304        "
11305        .unindent(),
11306    );
11307
11308    cx.update_editor(|editor, window, cx| {
11309        editor.backspace(&Default::default(), window, cx);
11310        editor.backspace(&Default::default(), window, cx);
11311    });
11312
11313    cx.assert_editor_state(
11314        &"
11315            ˇ
11316            ˇ]]
11317            ˇ
11318        "
11319        .unindent(),
11320    );
11321
11322    cx.update_editor(|editor, window, cx| {
11323        editor.handle_input("{", window, cx);
11324        editor.handle_input("{", window, cx);
11325        editor.move_right(&MoveRight, window, cx);
11326        editor.move_right(&MoveRight, window, cx);
11327        editor.move_left(&MoveLeft, window, cx);
11328        editor.move_left(&MoveLeft, window, cx);
11329        editor.backspace(&Default::default(), window, cx);
11330    });
11331
11332    cx.assert_editor_state(
11333        &"
11334            {ˇ}
11335            {ˇ}]]
11336            {ˇ}
11337        "
11338        .unindent(),
11339    );
11340
11341    cx.update_editor(|editor, window, cx| {
11342        editor.backspace(&Default::default(), window, cx);
11343    });
11344
11345    cx.assert_editor_state(
11346        &"
11347            ˇ
11348            ˇ]]
11349            ˇ
11350        "
11351        .unindent(),
11352    );
11353}
11354
11355#[gpui::test]
11356async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11357    init_test(cx, |_| {});
11358
11359    let language = Arc::new(Language::new(
11360        LanguageConfig::default(),
11361        Some(tree_sitter_rust::LANGUAGE.into()),
11362    ));
11363
11364    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11365    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11366    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11367    editor
11368        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11369        .await;
11370
11371    editor.update_in(cx, |editor, window, cx| {
11372        editor.set_auto_replace_emoji_shortcode(true);
11373
11374        editor.handle_input("Hello ", window, cx);
11375        editor.handle_input(":wave", window, cx);
11376        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11377
11378        editor.handle_input(":", window, cx);
11379        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11380
11381        editor.handle_input(" :smile", window, cx);
11382        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11383
11384        editor.handle_input(":", window, cx);
11385        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11386
11387        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11388        editor.handle_input(":wave", window, cx);
11389        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11390
11391        editor.handle_input(":", window, cx);
11392        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11393
11394        editor.handle_input(":1", window, cx);
11395        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11396
11397        editor.handle_input(":", window, cx);
11398        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11399
11400        // Ensure shortcode does not get replaced when it is part of a word
11401        editor.handle_input(" Test:wave", window, cx);
11402        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11403
11404        editor.handle_input(":", window, cx);
11405        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11406
11407        editor.set_auto_replace_emoji_shortcode(false);
11408
11409        // Ensure shortcode does not get replaced when auto replace is off
11410        editor.handle_input(" :wave", window, cx);
11411        assert_eq!(
11412            editor.text(cx),
11413            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11414        );
11415
11416        editor.handle_input(":", window, cx);
11417        assert_eq!(
11418            editor.text(cx),
11419            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11420        );
11421    });
11422}
11423
11424#[gpui::test]
11425async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11426    init_test(cx, |_| {});
11427
11428    let (text, insertion_ranges) = marked_text_ranges(
11429        indoc! {"
11430            ˇ
11431        "},
11432        false,
11433    );
11434
11435    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11436    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11437
11438    _ = editor.update_in(cx, |editor, window, cx| {
11439        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11440
11441        editor
11442            .insert_snippet(
11443                &insertion_ranges
11444                    .iter()
11445                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11446                    .collect::<Vec<_>>(),
11447                snippet,
11448                window,
11449                cx,
11450            )
11451            .unwrap();
11452
11453        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11454            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11455            assert_eq!(editor.text(cx), expected_text);
11456            assert_eq!(
11457                editor.selections.ranges(&editor.display_snapshot(cx)),
11458                selection_ranges
11459                    .iter()
11460                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11461                    .collect::<Vec<_>>()
11462            );
11463        }
11464
11465        assert(
11466            editor,
11467            cx,
11468            indoc! {"
11469            type «» =•
11470            "},
11471        );
11472
11473        assert!(editor.context_menu_visible(), "There should be a matches");
11474    });
11475}
11476
11477#[gpui::test]
11478async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11479    init_test(cx, |_| {});
11480
11481    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11482        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11483        assert_eq!(editor.text(cx), expected_text);
11484        assert_eq!(
11485            editor.selections.ranges(&editor.display_snapshot(cx)),
11486            selection_ranges
11487                .iter()
11488                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11489                .collect::<Vec<_>>()
11490        );
11491    }
11492
11493    let (text, insertion_ranges) = marked_text_ranges(
11494        indoc! {"
11495            ˇ
11496        "},
11497        false,
11498    );
11499
11500    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11501    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11502
11503    _ = editor.update_in(cx, |editor, window, cx| {
11504        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11505
11506        editor
11507            .insert_snippet(
11508                &insertion_ranges
11509                    .iter()
11510                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11511                    .collect::<Vec<_>>(),
11512                snippet,
11513                window,
11514                cx,
11515            )
11516            .unwrap();
11517
11518        assert_state(
11519            editor,
11520            cx,
11521            indoc! {"
11522            type «» = ;•
11523            "},
11524        );
11525
11526        assert!(
11527            editor.context_menu_visible(),
11528            "Context menu should be visible for placeholder choices"
11529        );
11530
11531        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11532
11533        assert_state(
11534            editor,
11535            cx,
11536            indoc! {"
11537            type  = «»;•
11538            "},
11539        );
11540
11541        assert!(
11542            !editor.context_menu_visible(),
11543            "Context menu should be hidden after moving to next tabstop"
11544        );
11545
11546        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11547
11548        assert_state(
11549            editor,
11550            cx,
11551            indoc! {"
11552            type  = ; ˇ
11553            "},
11554        );
11555
11556        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11557
11558        assert_state(
11559            editor,
11560            cx,
11561            indoc! {"
11562            type  = ; ˇ
11563            "},
11564        );
11565    });
11566
11567    _ = editor.update_in(cx, |editor, window, cx| {
11568        editor.select_all(&SelectAll, window, cx);
11569        editor.backspace(&Backspace, window, cx);
11570
11571        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11572        let insertion_ranges = editor
11573            .selections
11574            .all(&editor.display_snapshot(cx))
11575            .iter()
11576            .map(|s| s.range())
11577            .collect::<Vec<_>>();
11578
11579        editor
11580            .insert_snippet(&insertion_ranges, snippet, window, cx)
11581            .unwrap();
11582
11583        assert_state(editor, cx, "fn «» = value;•");
11584
11585        assert!(
11586            editor.context_menu_visible(),
11587            "Context menu should be visible for placeholder choices"
11588        );
11589
11590        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11591
11592        assert_state(editor, cx, "fn  = «valueˇ»;•");
11593
11594        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11595
11596        assert_state(editor, cx, "fn «» = value;•");
11597
11598        assert!(
11599            editor.context_menu_visible(),
11600            "Context menu should be visible again after returning to first tabstop"
11601        );
11602
11603        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11604
11605        assert_state(editor, cx, "fn «» = value;•");
11606    });
11607}
11608
11609#[gpui::test]
11610async fn test_snippets(cx: &mut TestAppContext) {
11611    init_test(cx, |_| {});
11612
11613    let mut cx = EditorTestContext::new(cx).await;
11614
11615    cx.set_state(indoc! {"
11616        a.ˇ b
11617        a.ˇ b
11618        a.ˇ b
11619    "});
11620
11621    cx.update_editor(|editor, window, cx| {
11622        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11623        let insertion_ranges = editor
11624            .selections
11625            .all(&editor.display_snapshot(cx))
11626            .iter()
11627            .map(|s| s.range())
11628            .collect::<Vec<_>>();
11629        editor
11630            .insert_snippet(&insertion_ranges, snippet, window, cx)
11631            .unwrap();
11632    });
11633
11634    cx.assert_editor_state(indoc! {"
11635        a.f(«oneˇ», two, «threeˇ») b
11636        a.f(«oneˇ», two, «threeˇ») b
11637        a.f(«oneˇ», two, «threeˇ») b
11638    "});
11639
11640    // Can't move earlier than the first tab stop
11641    cx.update_editor(|editor, window, cx| {
11642        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11643    });
11644    cx.assert_editor_state(indoc! {"
11645        a.f(«oneˇ», two, «threeˇ») b
11646        a.f(«oneˇ», two, «threeˇ») b
11647        a.f(«oneˇ», two, «threeˇ») b
11648    "});
11649
11650    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11651    cx.assert_editor_state(indoc! {"
11652        a.f(one, «twoˇ», three) b
11653        a.f(one, «twoˇ», three) b
11654        a.f(one, «twoˇ», three) b
11655    "});
11656
11657    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11658    cx.assert_editor_state(indoc! {"
11659        a.f(«oneˇ», two, «threeˇ») b
11660        a.f(«oneˇ», two, «threeˇ») b
11661        a.f(«oneˇ», two, «threeˇ») b
11662    "});
11663
11664    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11665    cx.assert_editor_state(indoc! {"
11666        a.f(one, «twoˇ», three) b
11667        a.f(one, «twoˇ», three) b
11668        a.f(one, «twoˇ», three) b
11669    "});
11670    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11671    cx.assert_editor_state(indoc! {"
11672        a.f(one, two, three)ˇ b
11673        a.f(one, two, three)ˇ b
11674        a.f(one, two, three)ˇ b
11675    "});
11676
11677    // As soon as the last tab stop is reached, snippet state is gone
11678    cx.update_editor(|editor, window, cx| {
11679        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11680    });
11681    cx.assert_editor_state(indoc! {"
11682        a.f(one, two, three)ˇ b
11683        a.f(one, two, three)ˇ b
11684        a.f(one, two, three)ˇ b
11685    "});
11686}
11687
11688#[gpui::test]
11689async fn test_snippet_indentation(cx: &mut TestAppContext) {
11690    init_test(cx, |_| {});
11691
11692    let mut cx = EditorTestContext::new(cx).await;
11693
11694    cx.update_editor(|editor, window, cx| {
11695        let snippet = Snippet::parse(indoc! {"
11696            /*
11697             * Multiline comment with leading indentation
11698             *
11699             * $1
11700             */
11701            $0"})
11702        .unwrap();
11703        let insertion_ranges = editor
11704            .selections
11705            .all(&editor.display_snapshot(cx))
11706            .iter()
11707            .map(|s| s.range())
11708            .collect::<Vec<_>>();
11709        editor
11710            .insert_snippet(&insertion_ranges, snippet, window, cx)
11711            .unwrap();
11712    });
11713
11714    cx.assert_editor_state(indoc! {"
11715        /*
11716         * Multiline comment with leading indentation
11717         *
11718         * ˇ
11719         */
11720    "});
11721
11722    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11723    cx.assert_editor_state(indoc! {"
11724        /*
11725         * Multiline comment with leading indentation
11726         *
11727         *•
11728         */
11729        ˇ"});
11730}
11731
11732#[gpui::test]
11733async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11734    init_test(cx, |_| {});
11735
11736    let mut cx = EditorTestContext::new(cx).await;
11737    cx.update_editor(|editor, _, cx| {
11738        editor.project().unwrap().update(cx, |project, cx| {
11739            project.snippets().update(cx, |snippets, _cx| {
11740                let snippet = project::snippet_provider::Snippet {
11741                    prefix: vec!["multi word".to_string()],
11742                    body: "this is many words".to_string(),
11743                    description: Some("description".to_string()),
11744                    name: "multi-word snippet test".to_string(),
11745                };
11746                snippets.add_snippet_for_test(
11747                    None,
11748                    PathBuf::from("test_snippets.json"),
11749                    vec![Arc::new(snippet)],
11750                );
11751            });
11752        })
11753    });
11754
11755    for (input_to_simulate, should_match_snippet) in [
11756        ("m", true),
11757        ("m ", true),
11758        ("m w", true),
11759        ("aa m w", true),
11760        ("aa m g", false),
11761    ] {
11762        cx.set_state("ˇ");
11763        cx.simulate_input(input_to_simulate); // fails correctly
11764
11765        cx.update_editor(|editor, _, _| {
11766            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11767            else {
11768                assert!(!should_match_snippet); // no completions! don't even show the menu
11769                return;
11770            };
11771            assert!(context_menu.visible());
11772            let completions = context_menu.completions.borrow();
11773
11774            assert_eq!(!completions.is_empty(), should_match_snippet);
11775        });
11776    }
11777}
11778
11779#[gpui::test]
11780async fn test_document_format_during_save(cx: &mut TestAppContext) {
11781    init_test(cx, |_| {});
11782
11783    let fs = FakeFs::new(cx.executor());
11784    fs.insert_file(path!("/file.rs"), Default::default()).await;
11785
11786    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11787
11788    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11789    language_registry.add(rust_lang());
11790    let mut fake_servers = language_registry.register_fake_lsp(
11791        "Rust",
11792        FakeLspAdapter {
11793            capabilities: lsp::ServerCapabilities {
11794                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11795                ..Default::default()
11796            },
11797            ..Default::default()
11798        },
11799    );
11800
11801    let buffer = project
11802        .update(cx, |project, cx| {
11803            project.open_local_buffer(path!("/file.rs"), cx)
11804        })
11805        .await
11806        .unwrap();
11807
11808    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11809    let (editor, cx) = cx.add_window_view(|window, cx| {
11810        build_editor_with_project(project.clone(), buffer, window, cx)
11811    });
11812    editor.update_in(cx, |editor, window, cx| {
11813        editor.set_text("one\ntwo\nthree\n", window, cx)
11814    });
11815    assert!(cx.read(|cx| editor.is_dirty(cx)));
11816
11817    cx.executor().start_waiting();
11818    let fake_server = fake_servers.next().await.unwrap();
11819
11820    {
11821        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11822            move |params, _| async move {
11823                assert_eq!(
11824                    params.text_document.uri,
11825                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11826                );
11827                assert_eq!(params.options.tab_size, 4);
11828                Ok(Some(vec![lsp::TextEdit::new(
11829                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11830                    ", ".to_string(),
11831                )]))
11832            },
11833        );
11834        let save = editor
11835            .update_in(cx, |editor, window, cx| {
11836                editor.save(
11837                    SaveOptions {
11838                        format: true,
11839                        autosave: false,
11840                    },
11841                    project.clone(),
11842                    window,
11843                    cx,
11844                )
11845            })
11846            .unwrap();
11847        cx.executor().start_waiting();
11848        save.await;
11849
11850        assert_eq!(
11851            editor.update(cx, |editor, cx| editor.text(cx)),
11852            "one, two\nthree\n"
11853        );
11854        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11855    }
11856
11857    {
11858        editor.update_in(cx, |editor, window, cx| {
11859            editor.set_text("one\ntwo\nthree\n", window, cx)
11860        });
11861        assert!(cx.read(|cx| editor.is_dirty(cx)));
11862
11863        // Ensure we can still save even if formatting hangs.
11864        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11865            move |params, _| async move {
11866                assert_eq!(
11867                    params.text_document.uri,
11868                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11869                );
11870                futures::future::pending::<()>().await;
11871                unreachable!()
11872            },
11873        );
11874        let save = editor
11875            .update_in(cx, |editor, window, cx| {
11876                editor.save(
11877                    SaveOptions {
11878                        format: true,
11879                        autosave: false,
11880                    },
11881                    project.clone(),
11882                    window,
11883                    cx,
11884                )
11885            })
11886            .unwrap();
11887        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11888        cx.executor().start_waiting();
11889        save.await;
11890        assert_eq!(
11891            editor.update(cx, |editor, cx| editor.text(cx)),
11892            "one\ntwo\nthree\n"
11893        );
11894    }
11895
11896    // Set rust language override and assert overridden tabsize is sent to language server
11897    update_test_language_settings(cx, |settings| {
11898        settings.languages.0.insert(
11899            "Rust".into(),
11900            LanguageSettingsContent {
11901                tab_size: NonZeroU32::new(8),
11902                ..Default::default()
11903            },
11904        );
11905    });
11906
11907    {
11908        editor.update_in(cx, |editor, window, cx| {
11909            editor.set_text("somehting_new\n", window, cx)
11910        });
11911        assert!(cx.read(|cx| editor.is_dirty(cx)));
11912        let _formatting_request_signal = fake_server
11913            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11914                assert_eq!(
11915                    params.text_document.uri,
11916                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11917                );
11918                assert_eq!(params.options.tab_size, 8);
11919                Ok(Some(vec![]))
11920            });
11921        let save = editor
11922            .update_in(cx, |editor, window, cx| {
11923                editor.save(
11924                    SaveOptions {
11925                        format: true,
11926                        autosave: false,
11927                    },
11928                    project.clone(),
11929                    window,
11930                    cx,
11931                )
11932            })
11933            .unwrap();
11934        cx.executor().start_waiting();
11935        save.await;
11936    }
11937}
11938
11939#[gpui::test]
11940async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11941    init_test(cx, |settings| {
11942        settings.defaults.ensure_final_newline_on_save = Some(false);
11943    });
11944
11945    let fs = FakeFs::new(cx.executor());
11946    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11947
11948    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11949
11950    let buffer = project
11951        .update(cx, |project, cx| {
11952            project.open_local_buffer(path!("/file.txt"), cx)
11953        })
11954        .await
11955        .unwrap();
11956
11957    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11958    let (editor, cx) = cx.add_window_view(|window, cx| {
11959        build_editor_with_project(project.clone(), buffer, window, cx)
11960    });
11961    editor.update_in(cx, |editor, window, cx| {
11962        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11963            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11964        });
11965    });
11966    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11967
11968    editor.update_in(cx, |editor, window, cx| {
11969        editor.handle_input("\n", window, cx)
11970    });
11971    cx.run_until_parked();
11972    save(&editor, &project, cx).await;
11973    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11974
11975    editor.update_in(cx, |editor, window, cx| {
11976        editor.undo(&Default::default(), window, cx);
11977    });
11978    save(&editor, &project, cx).await;
11979    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11980
11981    editor.update_in(cx, |editor, window, cx| {
11982        editor.redo(&Default::default(), window, cx);
11983    });
11984    cx.run_until_parked();
11985    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11986
11987    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11988        let save = editor
11989            .update_in(cx, |editor, window, cx| {
11990                editor.save(
11991                    SaveOptions {
11992                        format: true,
11993                        autosave: false,
11994                    },
11995                    project.clone(),
11996                    window,
11997                    cx,
11998                )
11999            })
12000            .unwrap();
12001        cx.executor().start_waiting();
12002        save.await;
12003        assert!(!cx.read(|cx| editor.is_dirty(cx)));
12004    }
12005}
12006
12007#[gpui::test]
12008async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
12009    init_test(cx, |_| {});
12010
12011    let cols = 4;
12012    let rows = 10;
12013    let sample_text_1 = sample_text(rows, cols, 'a');
12014    assert_eq!(
12015        sample_text_1,
12016        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
12017    );
12018    let sample_text_2 = sample_text(rows, cols, 'l');
12019    assert_eq!(
12020        sample_text_2,
12021        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
12022    );
12023    let sample_text_3 = sample_text(rows, cols, 'v');
12024    assert_eq!(
12025        sample_text_3,
12026        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
12027    );
12028
12029    let fs = FakeFs::new(cx.executor());
12030    fs.insert_tree(
12031        path!("/a"),
12032        json!({
12033            "main.rs": sample_text_1,
12034            "other.rs": sample_text_2,
12035            "lib.rs": sample_text_3,
12036        }),
12037    )
12038    .await;
12039
12040    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
12041    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12042    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12043
12044    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12045    language_registry.add(rust_lang());
12046    let mut fake_servers = language_registry.register_fake_lsp(
12047        "Rust",
12048        FakeLspAdapter {
12049            capabilities: lsp::ServerCapabilities {
12050                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12051                ..Default::default()
12052            },
12053            ..Default::default()
12054        },
12055    );
12056
12057    let worktree = project.update(cx, |project, cx| {
12058        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
12059        assert_eq!(worktrees.len(), 1);
12060        worktrees.pop().unwrap()
12061    });
12062    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12063
12064    let buffer_1 = project
12065        .update(cx, |project, cx| {
12066            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
12067        })
12068        .await
12069        .unwrap();
12070    let buffer_2 = project
12071        .update(cx, |project, cx| {
12072            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
12073        })
12074        .await
12075        .unwrap();
12076    let buffer_3 = project
12077        .update(cx, |project, cx| {
12078            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
12079        })
12080        .await
12081        .unwrap();
12082
12083    let multi_buffer = cx.new(|cx| {
12084        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12085        multi_buffer.push_excerpts(
12086            buffer_1.clone(),
12087            [
12088                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12089                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12090                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12091            ],
12092            cx,
12093        );
12094        multi_buffer.push_excerpts(
12095            buffer_2.clone(),
12096            [
12097                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12098                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12099                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12100            ],
12101            cx,
12102        );
12103        multi_buffer.push_excerpts(
12104            buffer_3.clone(),
12105            [
12106                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
12107                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
12108                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
12109            ],
12110            cx,
12111        );
12112        multi_buffer
12113    });
12114    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12115        Editor::new(
12116            EditorMode::full(),
12117            multi_buffer,
12118            Some(project.clone()),
12119            window,
12120            cx,
12121        )
12122    });
12123
12124    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12125        editor.change_selections(
12126            SelectionEffects::scroll(Autoscroll::Next),
12127            window,
12128            cx,
12129            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12130        );
12131        editor.insert("|one|two|three|", window, cx);
12132    });
12133    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12134    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12135        editor.change_selections(
12136            SelectionEffects::scroll(Autoscroll::Next),
12137            window,
12138            cx,
12139            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12140        );
12141        editor.insert("|four|five|six|", window, cx);
12142    });
12143    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12144
12145    // First two buffers should be edited, but not the third one.
12146    assert_eq!(
12147        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12148        "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}",
12149    );
12150    buffer_1.update(cx, |buffer, _| {
12151        assert!(buffer.is_dirty());
12152        assert_eq!(
12153            buffer.text(),
12154            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12155        )
12156    });
12157    buffer_2.update(cx, |buffer, _| {
12158        assert!(buffer.is_dirty());
12159        assert_eq!(
12160            buffer.text(),
12161            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12162        )
12163    });
12164    buffer_3.update(cx, |buffer, _| {
12165        assert!(!buffer.is_dirty());
12166        assert_eq!(buffer.text(), sample_text_3,)
12167    });
12168    cx.executor().run_until_parked();
12169
12170    cx.executor().start_waiting();
12171    let save = multi_buffer_editor
12172        .update_in(cx, |editor, window, cx| {
12173            editor.save(
12174                SaveOptions {
12175                    format: true,
12176                    autosave: false,
12177                },
12178                project.clone(),
12179                window,
12180                cx,
12181            )
12182        })
12183        .unwrap();
12184
12185    let fake_server = fake_servers.next().await.unwrap();
12186    fake_server
12187        .server
12188        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12189            Ok(Some(vec![lsp::TextEdit::new(
12190                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12191                format!("[{} formatted]", params.text_document.uri),
12192            )]))
12193        })
12194        .detach();
12195    save.await;
12196
12197    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12198    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12199    assert_eq!(
12200        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12201        uri!(
12202            "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}"
12203        ),
12204    );
12205    buffer_1.update(cx, |buffer, _| {
12206        assert!(!buffer.is_dirty());
12207        assert_eq!(
12208            buffer.text(),
12209            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12210        )
12211    });
12212    buffer_2.update(cx, |buffer, _| {
12213        assert!(!buffer.is_dirty());
12214        assert_eq!(
12215            buffer.text(),
12216            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12217        )
12218    });
12219    buffer_3.update(cx, |buffer, _| {
12220        assert!(!buffer.is_dirty());
12221        assert_eq!(buffer.text(), sample_text_3,)
12222    });
12223}
12224
12225#[gpui::test]
12226async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12227    init_test(cx, |_| {});
12228
12229    let fs = FakeFs::new(cx.executor());
12230    fs.insert_tree(
12231        path!("/dir"),
12232        json!({
12233            "file1.rs": "fn main() { println!(\"hello\"); }",
12234            "file2.rs": "fn test() { println!(\"test\"); }",
12235            "file3.rs": "fn other() { println!(\"other\"); }\n",
12236        }),
12237    )
12238    .await;
12239
12240    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12241    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12242    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12243
12244    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12245    language_registry.add(rust_lang());
12246
12247    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12248    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12249
12250    // Open three buffers
12251    let buffer_1 = project
12252        .update(cx, |project, cx| {
12253            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12254        })
12255        .await
12256        .unwrap();
12257    let buffer_2 = project
12258        .update(cx, |project, cx| {
12259            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12260        })
12261        .await
12262        .unwrap();
12263    let buffer_3 = project
12264        .update(cx, |project, cx| {
12265            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12266        })
12267        .await
12268        .unwrap();
12269
12270    // Create a multi-buffer with all three buffers
12271    let multi_buffer = cx.new(|cx| {
12272        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12273        multi_buffer.push_excerpts(
12274            buffer_1.clone(),
12275            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12276            cx,
12277        );
12278        multi_buffer.push_excerpts(
12279            buffer_2.clone(),
12280            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12281            cx,
12282        );
12283        multi_buffer.push_excerpts(
12284            buffer_3.clone(),
12285            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12286            cx,
12287        );
12288        multi_buffer
12289    });
12290
12291    let editor = cx.new_window_entity(|window, cx| {
12292        Editor::new(
12293            EditorMode::full(),
12294            multi_buffer,
12295            Some(project.clone()),
12296            window,
12297            cx,
12298        )
12299    });
12300
12301    // Edit only the first buffer
12302    editor.update_in(cx, |editor, window, cx| {
12303        editor.change_selections(
12304            SelectionEffects::scroll(Autoscroll::Next),
12305            window,
12306            cx,
12307            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12308        );
12309        editor.insert("// edited", window, cx);
12310    });
12311
12312    // Verify that only buffer 1 is dirty
12313    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12314    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12315    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12316
12317    // Get write counts after file creation (files were created with initial content)
12318    // We expect each file to have been written once during creation
12319    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12320    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12321    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12322
12323    // Perform autosave
12324    let save_task = editor.update_in(cx, |editor, window, cx| {
12325        editor.save(
12326            SaveOptions {
12327                format: true,
12328                autosave: true,
12329            },
12330            project.clone(),
12331            window,
12332            cx,
12333        )
12334    });
12335    save_task.await.unwrap();
12336
12337    // Only the dirty buffer should have been saved
12338    assert_eq!(
12339        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12340        1,
12341        "Buffer 1 was dirty, so it should have been written once during autosave"
12342    );
12343    assert_eq!(
12344        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12345        0,
12346        "Buffer 2 was clean, so it should not have been written during autosave"
12347    );
12348    assert_eq!(
12349        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12350        0,
12351        "Buffer 3 was clean, so it should not have been written during autosave"
12352    );
12353
12354    // Verify buffer states after autosave
12355    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12356    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12357    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12358
12359    // Now perform a manual save (format = true)
12360    let save_task = editor.update_in(cx, |editor, window, cx| {
12361        editor.save(
12362            SaveOptions {
12363                format: true,
12364                autosave: false,
12365            },
12366            project.clone(),
12367            window,
12368            cx,
12369        )
12370    });
12371    save_task.await.unwrap();
12372
12373    // During manual save, clean buffers don't get written to disk
12374    // They just get did_save called for language server notifications
12375    assert_eq!(
12376        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12377        1,
12378        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12379    );
12380    assert_eq!(
12381        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12382        0,
12383        "Buffer 2 should not have been written at all"
12384    );
12385    assert_eq!(
12386        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12387        0,
12388        "Buffer 3 should not have been written at all"
12389    );
12390}
12391
12392async fn setup_range_format_test(
12393    cx: &mut TestAppContext,
12394) -> (
12395    Entity<Project>,
12396    Entity<Editor>,
12397    &mut gpui::VisualTestContext,
12398    lsp::FakeLanguageServer,
12399) {
12400    init_test(cx, |_| {});
12401
12402    let fs = FakeFs::new(cx.executor());
12403    fs.insert_file(path!("/file.rs"), Default::default()).await;
12404
12405    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12406
12407    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12408    language_registry.add(rust_lang());
12409    let mut fake_servers = language_registry.register_fake_lsp(
12410        "Rust",
12411        FakeLspAdapter {
12412            capabilities: lsp::ServerCapabilities {
12413                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12414                ..lsp::ServerCapabilities::default()
12415            },
12416            ..FakeLspAdapter::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
12432    cx.executor().start_waiting();
12433    let fake_server = fake_servers.next().await.unwrap();
12434
12435    (project, editor, cx, fake_server)
12436}
12437
12438#[gpui::test]
12439async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12440    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12441
12442    editor.update_in(cx, |editor, window, cx| {
12443        editor.set_text("one\ntwo\nthree\n", window, cx)
12444    });
12445    assert!(cx.read(|cx| editor.is_dirty(cx)));
12446
12447    let save = editor
12448        .update_in(cx, |editor, window, cx| {
12449            editor.save(
12450                SaveOptions {
12451                    format: true,
12452                    autosave: false,
12453                },
12454                project.clone(),
12455                window,
12456                cx,
12457            )
12458        })
12459        .unwrap();
12460    fake_server
12461        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12462            assert_eq!(
12463                params.text_document.uri,
12464                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12465            );
12466            assert_eq!(params.options.tab_size, 4);
12467            Ok(Some(vec![lsp::TextEdit::new(
12468                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12469                ", ".to_string(),
12470            )]))
12471        })
12472        .next()
12473        .await;
12474    cx.executor().start_waiting();
12475    save.await;
12476    assert_eq!(
12477        editor.update(cx, |editor, cx| editor.text(cx)),
12478        "one, two\nthree\n"
12479    );
12480    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12481}
12482
12483#[gpui::test]
12484async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12485    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12486
12487    editor.update_in(cx, |editor, window, cx| {
12488        editor.set_text("one\ntwo\nthree\n", window, cx)
12489    });
12490    assert!(cx.read(|cx| editor.is_dirty(cx)));
12491
12492    // Test that save still works when formatting hangs
12493    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12494        move |params, _| async move {
12495            assert_eq!(
12496                params.text_document.uri,
12497                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12498            );
12499            futures::future::pending::<()>().await;
12500            unreachable!()
12501        },
12502    );
12503    let save = editor
12504        .update_in(cx, |editor, window, cx| {
12505            editor.save(
12506                SaveOptions {
12507                    format: true,
12508                    autosave: false,
12509                },
12510                project.clone(),
12511                window,
12512                cx,
12513            )
12514        })
12515        .unwrap();
12516    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12517    cx.executor().start_waiting();
12518    save.await;
12519    assert_eq!(
12520        editor.update(cx, |editor, cx| editor.text(cx)),
12521        "one\ntwo\nthree\n"
12522    );
12523    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12524}
12525
12526#[gpui::test]
12527async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12528    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12529
12530    // Buffer starts clean, no formatting should be requested
12531    let save = editor
12532        .update_in(cx, |editor, window, cx| {
12533            editor.save(
12534                SaveOptions {
12535                    format: false,
12536                    autosave: false,
12537                },
12538                project.clone(),
12539                window,
12540                cx,
12541            )
12542        })
12543        .unwrap();
12544    let _pending_format_request = fake_server
12545        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12546            panic!("Should not be invoked");
12547        })
12548        .next();
12549    cx.executor().start_waiting();
12550    save.await;
12551    cx.run_until_parked();
12552}
12553
12554#[gpui::test]
12555async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12556    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12557
12558    // Set Rust language override and assert overridden tabsize is sent to language server
12559    update_test_language_settings(cx, |settings| {
12560        settings.languages.0.insert(
12561            "Rust".into(),
12562            LanguageSettingsContent {
12563                tab_size: NonZeroU32::new(8),
12564                ..Default::default()
12565            },
12566        );
12567    });
12568
12569    editor.update_in(cx, |editor, window, cx| {
12570        editor.set_text("something_new\n", window, cx)
12571    });
12572    assert!(cx.read(|cx| editor.is_dirty(cx)));
12573    let save = editor
12574        .update_in(cx, |editor, window, cx| {
12575            editor.save(
12576                SaveOptions {
12577                    format: true,
12578                    autosave: false,
12579                },
12580                project.clone(),
12581                window,
12582                cx,
12583            )
12584        })
12585        .unwrap();
12586    fake_server
12587        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12588            assert_eq!(
12589                params.text_document.uri,
12590                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12591            );
12592            assert_eq!(params.options.tab_size, 8);
12593            Ok(Some(Vec::new()))
12594        })
12595        .next()
12596        .await;
12597    save.await;
12598}
12599
12600#[gpui::test]
12601async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12602    init_test(cx, |settings| {
12603        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12604            settings::LanguageServerFormatterSpecifier::Current,
12605        )))
12606    });
12607
12608    let fs = FakeFs::new(cx.executor());
12609    fs.insert_file(path!("/file.rs"), Default::default()).await;
12610
12611    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12612
12613    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12614    language_registry.add(Arc::new(Language::new(
12615        LanguageConfig {
12616            name: "Rust".into(),
12617            matcher: LanguageMatcher {
12618                path_suffixes: vec!["rs".to_string()],
12619                ..Default::default()
12620            },
12621            ..LanguageConfig::default()
12622        },
12623        Some(tree_sitter_rust::LANGUAGE.into()),
12624    )));
12625    update_test_language_settings(cx, |settings| {
12626        // Enable Prettier formatting for the same buffer, and ensure
12627        // LSP is called instead of Prettier.
12628        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12629    });
12630    let mut fake_servers = language_registry.register_fake_lsp(
12631        "Rust",
12632        FakeLspAdapter {
12633            capabilities: lsp::ServerCapabilities {
12634                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12635                ..Default::default()
12636            },
12637            ..Default::default()
12638        },
12639    );
12640
12641    let buffer = project
12642        .update(cx, |project, cx| {
12643            project.open_local_buffer(path!("/file.rs"), cx)
12644        })
12645        .await
12646        .unwrap();
12647
12648    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12649    let (editor, cx) = cx.add_window_view(|window, cx| {
12650        build_editor_with_project(project.clone(), buffer, window, cx)
12651    });
12652    editor.update_in(cx, |editor, window, cx| {
12653        editor.set_text("one\ntwo\nthree\n", window, cx)
12654    });
12655
12656    cx.executor().start_waiting();
12657    let fake_server = fake_servers.next().await.unwrap();
12658
12659    let format = editor
12660        .update_in(cx, |editor, window, cx| {
12661            editor.perform_format(
12662                project.clone(),
12663                FormatTrigger::Manual,
12664                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12665                window,
12666                cx,
12667            )
12668        })
12669        .unwrap();
12670    fake_server
12671        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12672            assert_eq!(
12673                params.text_document.uri,
12674                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12675            );
12676            assert_eq!(params.options.tab_size, 4);
12677            Ok(Some(vec![lsp::TextEdit::new(
12678                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12679                ", ".to_string(),
12680            )]))
12681        })
12682        .next()
12683        .await;
12684    cx.executor().start_waiting();
12685    format.await;
12686    assert_eq!(
12687        editor.update(cx, |editor, cx| editor.text(cx)),
12688        "one, two\nthree\n"
12689    );
12690
12691    editor.update_in(cx, |editor, window, cx| {
12692        editor.set_text("one\ntwo\nthree\n", window, cx)
12693    });
12694    // Ensure we don't lock if formatting hangs.
12695    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12696        move |params, _| async move {
12697            assert_eq!(
12698                params.text_document.uri,
12699                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12700            );
12701            futures::future::pending::<()>().await;
12702            unreachable!()
12703        },
12704    );
12705    let format = editor
12706        .update_in(cx, |editor, window, cx| {
12707            editor.perform_format(
12708                project,
12709                FormatTrigger::Manual,
12710                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12711                window,
12712                cx,
12713            )
12714        })
12715        .unwrap();
12716    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12717    cx.executor().start_waiting();
12718    format.await;
12719    assert_eq!(
12720        editor.update(cx, |editor, cx| editor.text(cx)),
12721        "one\ntwo\nthree\n"
12722    );
12723}
12724
12725#[gpui::test]
12726async fn test_multiple_formatters(cx: &mut TestAppContext) {
12727    init_test(cx, |settings| {
12728        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12729        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12730            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12731            Formatter::CodeAction("code-action-1".into()),
12732            Formatter::CodeAction("code-action-2".into()),
12733        ]))
12734    });
12735
12736    let fs = FakeFs::new(cx.executor());
12737    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12738        .await;
12739
12740    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12741    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12742    language_registry.add(rust_lang());
12743
12744    let mut fake_servers = language_registry.register_fake_lsp(
12745        "Rust",
12746        FakeLspAdapter {
12747            capabilities: lsp::ServerCapabilities {
12748                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12749                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12750                    commands: vec!["the-command-for-code-action-1".into()],
12751                    ..Default::default()
12752                }),
12753                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12754                ..Default::default()
12755            },
12756            ..Default::default()
12757        },
12758    );
12759
12760    let buffer = project
12761        .update(cx, |project, cx| {
12762            project.open_local_buffer(path!("/file.rs"), cx)
12763        })
12764        .await
12765        .unwrap();
12766
12767    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12768    let (editor, cx) = cx.add_window_view(|window, cx| {
12769        build_editor_with_project(project.clone(), buffer, window, cx)
12770    });
12771
12772    cx.executor().start_waiting();
12773
12774    let fake_server = fake_servers.next().await.unwrap();
12775    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12776        move |_params, _| async move {
12777            Ok(Some(vec![lsp::TextEdit::new(
12778                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12779                "applied-formatting\n".to_string(),
12780            )]))
12781        },
12782    );
12783    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12784        move |params, _| async move {
12785            let requested_code_actions = params.context.only.expect("Expected code action request");
12786            assert_eq!(requested_code_actions.len(), 1);
12787
12788            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12789            let code_action = match requested_code_actions[0].as_str() {
12790                "code-action-1" => lsp::CodeAction {
12791                    kind: Some("code-action-1".into()),
12792                    edit: Some(lsp::WorkspaceEdit::new(
12793                        [(
12794                            uri,
12795                            vec![lsp::TextEdit::new(
12796                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12797                                "applied-code-action-1-edit\n".to_string(),
12798                            )],
12799                        )]
12800                        .into_iter()
12801                        .collect(),
12802                    )),
12803                    command: Some(lsp::Command {
12804                        command: "the-command-for-code-action-1".into(),
12805                        ..Default::default()
12806                    }),
12807                    ..Default::default()
12808                },
12809                "code-action-2" => lsp::CodeAction {
12810                    kind: Some("code-action-2".into()),
12811                    edit: Some(lsp::WorkspaceEdit::new(
12812                        [(
12813                            uri,
12814                            vec![lsp::TextEdit::new(
12815                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12816                                "applied-code-action-2-edit\n".to_string(),
12817                            )],
12818                        )]
12819                        .into_iter()
12820                        .collect(),
12821                    )),
12822                    ..Default::default()
12823                },
12824                req => panic!("Unexpected code action request: {:?}", req),
12825            };
12826            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12827                code_action,
12828            )]))
12829        },
12830    );
12831
12832    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12833        move |params, _| async move { Ok(params) }
12834    });
12835
12836    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12837    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12838        let fake = fake_server.clone();
12839        let lock = command_lock.clone();
12840        move |params, _| {
12841            assert_eq!(params.command, "the-command-for-code-action-1");
12842            let fake = fake.clone();
12843            let lock = lock.clone();
12844            async move {
12845                lock.lock().await;
12846                fake.server
12847                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12848                        label: None,
12849                        edit: lsp::WorkspaceEdit {
12850                            changes: Some(
12851                                [(
12852                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12853                                    vec![lsp::TextEdit {
12854                                        range: lsp::Range::new(
12855                                            lsp::Position::new(0, 0),
12856                                            lsp::Position::new(0, 0),
12857                                        ),
12858                                        new_text: "applied-code-action-1-command\n".into(),
12859                                    }],
12860                                )]
12861                                .into_iter()
12862                                .collect(),
12863                            ),
12864                            ..Default::default()
12865                        },
12866                    })
12867                    .await
12868                    .into_response()
12869                    .unwrap();
12870                Ok(Some(json!(null)))
12871            }
12872        }
12873    });
12874
12875    cx.executor().start_waiting();
12876    editor
12877        .update_in(cx, |editor, window, cx| {
12878            editor.perform_format(
12879                project.clone(),
12880                FormatTrigger::Manual,
12881                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12882                window,
12883                cx,
12884            )
12885        })
12886        .unwrap()
12887        .await;
12888    editor.update(cx, |editor, cx| {
12889        assert_eq!(
12890            editor.text(cx),
12891            r#"
12892                applied-code-action-2-edit
12893                applied-code-action-1-command
12894                applied-code-action-1-edit
12895                applied-formatting
12896                one
12897                two
12898                three
12899            "#
12900            .unindent()
12901        );
12902    });
12903
12904    editor.update_in(cx, |editor, window, cx| {
12905        editor.undo(&Default::default(), window, cx);
12906        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12907    });
12908
12909    // Perform a manual edit while waiting for an LSP command
12910    // that's being run as part of a formatting code action.
12911    let lock_guard = command_lock.lock().await;
12912    let format = editor
12913        .update_in(cx, |editor, window, cx| {
12914            editor.perform_format(
12915                project.clone(),
12916                FormatTrigger::Manual,
12917                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12918                window,
12919                cx,
12920            )
12921        })
12922        .unwrap();
12923    cx.run_until_parked();
12924    editor.update(cx, |editor, cx| {
12925        assert_eq!(
12926            editor.text(cx),
12927            r#"
12928                applied-code-action-1-edit
12929                applied-formatting
12930                one
12931                two
12932                three
12933            "#
12934            .unindent()
12935        );
12936
12937        editor.buffer.update(cx, |buffer, cx| {
12938            let ix = buffer.len(cx);
12939            buffer.edit([(ix..ix, "edited\n")], None, cx);
12940        });
12941    });
12942
12943    // Allow the LSP command to proceed. Because the buffer was edited,
12944    // the second code action will not be run.
12945    drop(lock_guard);
12946    format.await;
12947    editor.update_in(cx, |editor, window, cx| {
12948        assert_eq!(
12949            editor.text(cx),
12950            r#"
12951                applied-code-action-1-command
12952                applied-code-action-1-edit
12953                applied-formatting
12954                one
12955                two
12956                three
12957                edited
12958            "#
12959            .unindent()
12960        );
12961
12962        // The manual edit is undone first, because it is the last thing the user did
12963        // (even though the command completed afterwards).
12964        editor.undo(&Default::default(), window, cx);
12965        assert_eq!(
12966            editor.text(cx),
12967            r#"
12968                applied-code-action-1-command
12969                applied-code-action-1-edit
12970                applied-formatting
12971                one
12972                two
12973                three
12974            "#
12975            .unindent()
12976        );
12977
12978        // All the formatting (including the command, which completed after the manual edit)
12979        // is undone together.
12980        editor.undo(&Default::default(), window, cx);
12981        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12982    });
12983}
12984
12985#[gpui::test]
12986async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12987    init_test(cx, |settings| {
12988        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12989            settings::LanguageServerFormatterSpecifier::Current,
12990        )]))
12991    });
12992
12993    let fs = FakeFs::new(cx.executor());
12994    fs.insert_file(path!("/file.ts"), Default::default()).await;
12995
12996    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12997
12998    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12999    language_registry.add(Arc::new(Language::new(
13000        LanguageConfig {
13001            name: "TypeScript".into(),
13002            matcher: LanguageMatcher {
13003                path_suffixes: vec!["ts".to_string()],
13004                ..Default::default()
13005            },
13006            ..LanguageConfig::default()
13007        },
13008        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
13009    )));
13010    update_test_language_settings(cx, |settings| {
13011        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
13012    });
13013    let mut fake_servers = language_registry.register_fake_lsp(
13014        "TypeScript",
13015        FakeLspAdapter {
13016            capabilities: lsp::ServerCapabilities {
13017                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
13018                ..Default::default()
13019            },
13020            ..Default::default()
13021        },
13022    );
13023
13024    let buffer = project
13025        .update(cx, |project, cx| {
13026            project.open_local_buffer(path!("/file.ts"), cx)
13027        })
13028        .await
13029        .unwrap();
13030
13031    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
13032    let (editor, cx) = cx.add_window_view(|window, cx| {
13033        build_editor_with_project(project.clone(), buffer, window, cx)
13034    });
13035    editor.update_in(cx, |editor, window, cx| {
13036        editor.set_text(
13037            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13038            window,
13039            cx,
13040        )
13041    });
13042
13043    cx.executor().start_waiting();
13044    let fake_server = fake_servers.next().await.unwrap();
13045
13046    let format = editor
13047        .update_in(cx, |editor, window, cx| {
13048            editor.perform_code_action_kind(
13049                project.clone(),
13050                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13051                window,
13052                cx,
13053            )
13054        })
13055        .unwrap();
13056    fake_server
13057        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
13058            assert_eq!(
13059                params.text_document.uri,
13060                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13061            );
13062            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
13063                lsp::CodeAction {
13064                    title: "Organize Imports".to_string(),
13065                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
13066                    edit: Some(lsp::WorkspaceEdit {
13067                        changes: Some(
13068                            [(
13069                                params.text_document.uri.clone(),
13070                                vec![lsp::TextEdit::new(
13071                                    lsp::Range::new(
13072                                        lsp::Position::new(1, 0),
13073                                        lsp::Position::new(2, 0),
13074                                    ),
13075                                    "".to_string(),
13076                                )],
13077                            )]
13078                            .into_iter()
13079                            .collect(),
13080                        ),
13081                        ..Default::default()
13082                    }),
13083                    ..Default::default()
13084                },
13085            )]))
13086        })
13087        .next()
13088        .await;
13089    cx.executor().start_waiting();
13090    format.await;
13091    assert_eq!(
13092        editor.update(cx, |editor, cx| editor.text(cx)),
13093        "import { a } from 'module';\n\nconst x = a;\n"
13094    );
13095
13096    editor.update_in(cx, |editor, window, cx| {
13097        editor.set_text(
13098            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
13099            window,
13100            cx,
13101        )
13102    });
13103    // Ensure we don't lock if code action hangs.
13104    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
13105        move |params, _| async move {
13106            assert_eq!(
13107                params.text_document.uri,
13108                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
13109            );
13110            futures::future::pending::<()>().await;
13111            unreachable!()
13112        },
13113    );
13114    let format = editor
13115        .update_in(cx, |editor, window, cx| {
13116            editor.perform_code_action_kind(
13117                project,
13118                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13119                window,
13120                cx,
13121            )
13122        })
13123        .unwrap();
13124    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13125    cx.executor().start_waiting();
13126    format.await;
13127    assert_eq!(
13128        editor.update(cx, |editor, cx| editor.text(cx)),
13129        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13130    );
13131}
13132
13133#[gpui::test]
13134async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13135    init_test(cx, |_| {});
13136
13137    let mut cx = EditorLspTestContext::new_rust(
13138        lsp::ServerCapabilities {
13139            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13140            ..Default::default()
13141        },
13142        cx,
13143    )
13144    .await;
13145
13146    cx.set_state(indoc! {"
13147        one.twoˇ
13148    "});
13149
13150    // The format request takes a long time. When it completes, it inserts
13151    // a newline and an indent before the `.`
13152    cx.lsp
13153        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13154            let executor = cx.background_executor().clone();
13155            async move {
13156                executor.timer(Duration::from_millis(100)).await;
13157                Ok(Some(vec![lsp::TextEdit {
13158                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13159                    new_text: "\n    ".into(),
13160                }]))
13161            }
13162        });
13163
13164    // Submit a format request.
13165    let format_1 = cx
13166        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13167        .unwrap();
13168    cx.executor().run_until_parked();
13169
13170    // Submit a second format request.
13171    let format_2 = cx
13172        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13173        .unwrap();
13174    cx.executor().run_until_parked();
13175
13176    // Wait for both format requests to complete
13177    cx.executor().advance_clock(Duration::from_millis(200));
13178    cx.executor().start_waiting();
13179    format_1.await.unwrap();
13180    cx.executor().start_waiting();
13181    format_2.await.unwrap();
13182
13183    // The formatting edits only happens once.
13184    cx.assert_editor_state(indoc! {"
13185        one
13186            .twoˇ
13187    "});
13188}
13189
13190#[gpui::test]
13191async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13192    init_test(cx, |settings| {
13193        settings.defaults.formatter = Some(FormatterList::default())
13194    });
13195
13196    let mut cx = EditorLspTestContext::new_rust(
13197        lsp::ServerCapabilities {
13198            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13199            ..Default::default()
13200        },
13201        cx,
13202    )
13203    .await;
13204
13205    // Record which buffer changes have been sent to the language server
13206    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13207    cx.lsp
13208        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13209            let buffer_changes = buffer_changes.clone();
13210            move |params, _| {
13211                buffer_changes.lock().extend(
13212                    params
13213                        .content_changes
13214                        .into_iter()
13215                        .map(|e| (e.range.unwrap(), e.text)),
13216                );
13217            }
13218        });
13219    // Handle formatting requests to the language server.
13220    cx.lsp
13221        .set_request_handler::<lsp::request::Formatting, _, _>({
13222            let buffer_changes = buffer_changes.clone();
13223            move |_, _| {
13224                let buffer_changes = buffer_changes.clone();
13225                // Insert blank lines between each line of the buffer.
13226                async move {
13227                    // When formatting is requested, trailing whitespace has already been stripped,
13228                    // and the trailing newline has already been added.
13229                    assert_eq!(
13230                        &buffer_changes.lock()[1..],
13231                        &[
13232                            (
13233                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13234                                "".into()
13235                            ),
13236                            (
13237                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13238                                "".into()
13239                            ),
13240                            (
13241                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13242                                "\n".into()
13243                            ),
13244                        ]
13245                    );
13246
13247                    Ok(Some(vec![
13248                        lsp::TextEdit {
13249                            range: lsp::Range::new(
13250                                lsp::Position::new(1, 0),
13251                                lsp::Position::new(1, 0),
13252                            ),
13253                            new_text: "\n".into(),
13254                        },
13255                        lsp::TextEdit {
13256                            range: lsp::Range::new(
13257                                lsp::Position::new(2, 0),
13258                                lsp::Position::new(2, 0),
13259                            ),
13260                            new_text: "\n".into(),
13261                        },
13262                    ]))
13263                }
13264            }
13265        });
13266
13267    // Set up a buffer white some trailing whitespace and no trailing newline.
13268    cx.set_state(
13269        &[
13270            "one ",   //
13271            "twoˇ",   //
13272            "three ", //
13273            "four",   //
13274        ]
13275        .join("\n"),
13276    );
13277    cx.run_until_parked();
13278
13279    // Submit a format request.
13280    let format = cx
13281        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13282        .unwrap();
13283
13284    cx.run_until_parked();
13285    // After formatting the buffer, the trailing whitespace is stripped,
13286    // a newline is appended, and the edits provided by the language server
13287    // have been applied.
13288    format.await.unwrap();
13289
13290    cx.assert_editor_state(
13291        &[
13292            "one",   //
13293            "",      //
13294            "twoˇ",  //
13295            "",      //
13296            "three", //
13297            "four",  //
13298            "",      //
13299        ]
13300        .join("\n"),
13301    );
13302
13303    // Undoing the formatting undoes the trailing whitespace removal, the
13304    // trailing newline, and the LSP edits.
13305    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13306    cx.assert_editor_state(
13307        &[
13308            "one ",   //
13309            "twoˇ",   //
13310            "three ", //
13311            "four",   //
13312        ]
13313        .join("\n"),
13314    );
13315}
13316
13317#[gpui::test]
13318async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13319    cx: &mut TestAppContext,
13320) {
13321    init_test(cx, |_| {});
13322
13323    cx.update(|cx| {
13324        cx.update_global::<SettingsStore, _>(|settings, cx| {
13325            settings.update_user_settings(cx, |settings| {
13326                settings.editor.auto_signature_help = Some(true);
13327            });
13328        });
13329    });
13330
13331    let mut cx = EditorLspTestContext::new_rust(
13332        lsp::ServerCapabilities {
13333            signature_help_provider: Some(lsp::SignatureHelpOptions {
13334                ..Default::default()
13335            }),
13336            ..Default::default()
13337        },
13338        cx,
13339    )
13340    .await;
13341
13342    let language = Language::new(
13343        LanguageConfig {
13344            name: "Rust".into(),
13345            brackets: BracketPairConfig {
13346                pairs: vec![
13347                    BracketPair {
13348                        start: "{".to_string(),
13349                        end: "}".to_string(),
13350                        close: true,
13351                        surround: true,
13352                        newline: true,
13353                    },
13354                    BracketPair {
13355                        start: "(".to_string(),
13356                        end: ")".to_string(),
13357                        close: true,
13358                        surround: true,
13359                        newline: true,
13360                    },
13361                    BracketPair {
13362                        start: "/*".to_string(),
13363                        end: " */".to_string(),
13364                        close: true,
13365                        surround: true,
13366                        newline: true,
13367                    },
13368                    BracketPair {
13369                        start: "[".to_string(),
13370                        end: "]".to_string(),
13371                        close: false,
13372                        surround: false,
13373                        newline: true,
13374                    },
13375                    BracketPair {
13376                        start: "\"".to_string(),
13377                        end: "\"".to_string(),
13378                        close: true,
13379                        surround: true,
13380                        newline: false,
13381                    },
13382                    BracketPair {
13383                        start: "<".to_string(),
13384                        end: ">".to_string(),
13385                        close: false,
13386                        surround: true,
13387                        newline: true,
13388                    },
13389                ],
13390                ..Default::default()
13391            },
13392            autoclose_before: "})]".to_string(),
13393            ..Default::default()
13394        },
13395        Some(tree_sitter_rust::LANGUAGE.into()),
13396    );
13397    let language = Arc::new(language);
13398
13399    cx.language_registry().add(language.clone());
13400    cx.update_buffer(|buffer, cx| {
13401        buffer.set_language(Some(language), cx);
13402    });
13403
13404    cx.set_state(
13405        &r#"
13406            fn main() {
13407                sampleˇ
13408            }
13409        "#
13410        .unindent(),
13411    );
13412
13413    cx.update_editor(|editor, window, cx| {
13414        editor.handle_input("(", window, cx);
13415    });
13416    cx.assert_editor_state(
13417        &"
13418            fn main() {
13419                sample(ˇ)
13420            }
13421        "
13422        .unindent(),
13423    );
13424
13425    let mocked_response = lsp::SignatureHelp {
13426        signatures: vec![lsp::SignatureInformation {
13427            label: "fn sample(param1: u8, param2: u8)".to_string(),
13428            documentation: None,
13429            parameters: Some(vec![
13430                lsp::ParameterInformation {
13431                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13432                    documentation: None,
13433                },
13434                lsp::ParameterInformation {
13435                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13436                    documentation: None,
13437                },
13438            ]),
13439            active_parameter: None,
13440        }],
13441        active_signature: Some(0),
13442        active_parameter: Some(0),
13443    };
13444    handle_signature_help_request(&mut cx, mocked_response).await;
13445
13446    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13447        .await;
13448
13449    cx.editor(|editor, _, _| {
13450        let signature_help_state = editor.signature_help_state.popover().cloned();
13451        let signature = signature_help_state.unwrap();
13452        assert_eq!(
13453            signature.signatures[signature.current_signature].label,
13454            "fn sample(param1: u8, param2: u8)"
13455        );
13456    });
13457}
13458
13459#[gpui::test]
13460async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13461    init_test(cx, |_| {});
13462
13463    cx.update(|cx| {
13464        cx.update_global::<SettingsStore, _>(|settings, cx| {
13465            settings.update_user_settings(cx, |settings| {
13466                settings.editor.auto_signature_help = Some(false);
13467                settings.editor.show_signature_help_after_edits = Some(false);
13468            });
13469        });
13470    });
13471
13472    let mut cx = EditorLspTestContext::new_rust(
13473        lsp::ServerCapabilities {
13474            signature_help_provider: Some(lsp::SignatureHelpOptions {
13475                ..Default::default()
13476            }),
13477            ..Default::default()
13478        },
13479        cx,
13480    )
13481    .await;
13482
13483    let language = Language::new(
13484        LanguageConfig {
13485            name: "Rust".into(),
13486            brackets: BracketPairConfig {
13487                pairs: vec![
13488                    BracketPair {
13489                        start: "{".to_string(),
13490                        end: "}".to_string(),
13491                        close: true,
13492                        surround: true,
13493                        newline: true,
13494                    },
13495                    BracketPair {
13496                        start: "(".to_string(),
13497                        end: ")".to_string(),
13498                        close: true,
13499                        surround: true,
13500                        newline: true,
13501                    },
13502                    BracketPair {
13503                        start: "/*".to_string(),
13504                        end: " */".to_string(),
13505                        close: true,
13506                        surround: true,
13507                        newline: true,
13508                    },
13509                    BracketPair {
13510                        start: "[".to_string(),
13511                        end: "]".to_string(),
13512                        close: false,
13513                        surround: false,
13514                        newline: true,
13515                    },
13516                    BracketPair {
13517                        start: "\"".to_string(),
13518                        end: "\"".to_string(),
13519                        close: true,
13520                        surround: true,
13521                        newline: false,
13522                    },
13523                    BracketPair {
13524                        start: "<".to_string(),
13525                        end: ">".to_string(),
13526                        close: false,
13527                        surround: true,
13528                        newline: true,
13529                    },
13530                ],
13531                ..Default::default()
13532            },
13533            autoclose_before: "})]".to_string(),
13534            ..Default::default()
13535        },
13536        Some(tree_sitter_rust::LANGUAGE.into()),
13537    );
13538    let language = Arc::new(language);
13539
13540    cx.language_registry().add(language.clone());
13541    cx.update_buffer(|buffer, cx| {
13542        buffer.set_language(Some(language), cx);
13543    });
13544
13545    // Ensure that signature_help is not called when no signature help is enabled.
13546    cx.set_state(
13547        &r#"
13548            fn main() {
13549                sampleˇ
13550            }
13551        "#
13552        .unindent(),
13553    );
13554    cx.update_editor(|editor, window, cx| {
13555        editor.handle_input("(", window, cx);
13556    });
13557    cx.assert_editor_state(
13558        &"
13559            fn main() {
13560                sample(ˇ)
13561            }
13562        "
13563        .unindent(),
13564    );
13565    cx.editor(|editor, _, _| {
13566        assert!(editor.signature_help_state.task().is_none());
13567    });
13568
13569    let mocked_response = lsp::SignatureHelp {
13570        signatures: vec![lsp::SignatureInformation {
13571            label: "fn sample(param1: u8, param2: u8)".to_string(),
13572            documentation: None,
13573            parameters: Some(vec![
13574                lsp::ParameterInformation {
13575                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13576                    documentation: None,
13577                },
13578                lsp::ParameterInformation {
13579                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13580                    documentation: None,
13581                },
13582            ]),
13583            active_parameter: None,
13584        }],
13585        active_signature: Some(0),
13586        active_parameter: Some(0),
13587    };
13588
13589    // Ensure that signature_help is called when enabled afte edits
13590    cx.update(|_, cx| {
13591        cx.update_global::<SettingsStore, _>(|settings, cx| {
13592            settings.update_user_settings(cx, |settings| {
13593                settings.editor.auto_signature_help = Some(false);
13594                settings.editor.show_signature_help_after_edits = Some(true);
13595            });
13596        });
13597    });
13598    cx.set_state(
13599        &r#"
13600            fn main() {
13601                sampleˇ
13602            }
13603        "#
13604        .unindent(),
13605    );
13606    cx.update_editor(|editor, window, cx| {
13607        editor.handle_input("(", window, cx);
13608    });
13609    cx.assert_editor_state(
13610        &"
13611            fn main() {
13612                sample(ˇ)
13613            }
13614        "
13615        .unindent(),
13616    );
13617    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13618    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13619        .await;
13620    cx.update_editor(|editor, _, _| {
13621        let signature_help_state = editor.signature_help_state.popover().cloned();
13622        assert!(signature_help_state.is_some());
13623        let signature = signature_help_state.unwrap();
13624        assert_eq!(
13625            signature.signatures[signature.current_signature].label,
13626            "fn sample(param1: u8, param2: u8)"
13627        );
13628        editor.signature_help_state = SignatureHelpState::default();
13629    });
13630
13631    // Ensure that signature_help is called when auto signature help override is enabled
13632    cx.update(|_, cx| {
13633        cx.update_global::<SettingsStore, _>(|settings, cx| {
13634            settings.update_user_settings(cx, |settings| {
13635                settings.editor.auto_signature_help = Some(true);
13636                settings.editor.show_signature_help_after_edits = Some(false);
13637            });
13638        });
13639    });
13640    cx.set_state(
13641        &r#"
13642            fn main() {
13643                sampleˇ
13644            }
13645        "#
13646        .unindent(),
13647    );
13648    cx.update_editor(|editor, window, cx| {
13649        editor.handle_input("(", window, cx);
13650    });
13651    cx.assert_editor_state(
13652        &"
13653            fn main() {
13654                sample(ˇ)
13655            }
13656        "
13657        .unindent(),
13658    );
13659    handle_signature_help_request(&mut cx, mocked_response).await;
13660    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13661        .await;
13662    cx.editor(|editor, _, _| {
13663        let signature_help_state = editor.signature_help_state.popover().cloned();
13664        assert!(signature_help_state.is_some());
13665        let signature = signature_help_state.unwrap();
13666        assert_eq!(
13667            signature.signatures[signature.current_signature].label,
13668            "fn sample(param1: u8, param2: u8)"
13669        );
13670    });
13671}
13672
13673#[gpui::test]
13674async fn test_signature_help(cx: &mut TestAppContext) {
13675    init_test(cx, |_| {});
13676    cx.update(|cx| {
13677        cx.update_global::<SettingsStore, _>(|settings, cx| {
13678            settings.update_user_settings(cx, |settings| {
13679                settings.editor.auto_signature_help = Some(true);
13680            });
13681        });
13682    });
13683
13684    let mut cx = EditorLspTestContext::new_rust(
13685        lsp::ServerCapabilities {
13686            signature_help_provider: Some(lsp::SignatureHelpOptions {
13687                ..Default::default()
13688            }),
13689            ..Default::default()
13690        },
13691        cx,
13692    )
13693    .await;
13694
13695    // A test that directly calls `show_signature_help`
13696    cx.update_editor(|editor, window, cx| {
13697        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13698    });
13699
13700    let mocked_response = lsp::SignatureHelp {
13701        signatures: vec![lsp::SignatureInformation {
13702            label: "fn sample(param1: u8, param2: u8)".to_string(),
13703            documentation: None,
13704            parameters: Some(vec![
13705                lsp::ParameterInformation {
13706                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13707                    documentation: None,
13708                },
13709                lsp::ParameterInformation {
13710                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13711                    documentation: None,
13712                },
13713            ]),
13714            active_parameter: None,
13715        }],
13716        active_signature: Some(0),
13717        active_parameter: Some(0),
13718    };
13719    handle_signature_help_request(&mut cx, mocked_response).await;
13720
13721    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13722        .await;
13723
13724    cx.editor(|editor, _, _| {
13725        let signature_help_state = editor.signature_help_state.popover().cloned();
13726        assert!(signature_help_state.is_some());
13727        let signature = signature_help_state.unwrap();
13728        assert_eq!(
13729            signature.signatures[signature.current_signature].label,
13730            "fn sample(param1: u8, param2: u8)"
13731        );
13732    });
13733
13734    // When exiting outside from inside the brackets, `signature_help` is closed.
13735    cx.set_state(indoc! {"
13736        fn main() {
13737            sample(ˇ);
13738        }
13739
13740        fn sample(param1: u8, param2: u8) {}
13741    "});
13742
13743    cx.update_editor(|editor, window, cx| {
13744        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13745            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13746        });
13747    });
13748
13749    let mocked_response = lsp::SignatureHelp {
13750        signatures: Vec::new(),
13751        active_signature: None,
13752        active_parameter: None,
13753    };
13754    handle_signature_help_request(&mut cx, mocked_response).await;
13755
13756    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13757        .await;
13758
13759    cx.editor(|editor, _, _| {
13760        assert!(!editor.signature_help_state.is_shown());
13761    });
13762
13763    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13764    cx.set_state(indoc! {"
13765        fn main() {
13766            sample(ˇ);
13767        }
13768
13769        fn sample(param1: u8, param2: u8) {}
13770    "});
13771
13772    let mocked_response = lsp::SignatureHelp {
13773        signatures: vec![lsp::SignatureInformation {
13774            label: "fn sample(param1: u8, param2: u8)".to_string(),
13775            documentation: None,
13776            parameters: Some(vec![
13777                lsp::ParameterInformation {
13778                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13779                    documentation: None,
13780                },
13781                lsp::ParameterInformation {
13782                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13783                    documentation: None,
13784                },
13785            ]),
13786            active_parameter: None,
13787        }],
13788        active_signature: Some(0),
13789        active_parameter: Some(0),
13790    };
13791    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13792    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13793        .await;
13794    cx.editor(|editor, _, _| {
13795        assert!(editor.signature_help_state.is_shown());
13796    });
13797
13798    // Restore the popover with more parameter input
13799    cx.set_state(indoc! {"
13800        fn main() {
13801            sample(param1, param2ˇ);
13802        }
13803
13804        fn sample(param1: u8, param2: u8) {}
13805    "});
13806
13807    let mocked_response = lsp::SignatureHelp {
13808        signatures: vec![lsp::SignatureInformation {
13809            label: "fn sample(param1: u8, param2: u8)".to_string(),
13810            documentation: None,
13811            parameters: Some(vec![
13812                lsp::ParameterInformation {
13813                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13814                    documentation: None,
13815                },
13816                lsp::ParameterInformation {
13817                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13818                    documentation: None,
13819                },
13820            ]),
13821            active_parameter: None,
13822        }],
13823        active_signature: Some(0),
13824        active_parameter: Some(1),
13825    };
13826    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13827    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13828        .await;
13829
13830    // When selecting a range, the popover is gone.
13831    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13832    cx.update_editor(|editor, window, cx| {
13833        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13834            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13835        })
13836    });
13837    cx.assert_editor_state(indoc! {"
13838        fn main() {
13839            sample(param1, «ˇparam2»);
13840        }
13841
13842        fn sample(param1: u8, param2: u8) {}
13843    "});
13844    cx.editor(|editor, _, _| {
13845        assert!(!editor.signature_help_state.is_shown());
13846    });
13847
13848    // When unselecting again, the popover is back if within the brackets.
13849    cx.update_editor(|editor, window, cx| {
13850        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13851            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13852        })
13853    });
13854    cx.assert_editor_state(indoc! {"
13855        fn main() {
13856            sample(param1, ˇparam2);
13857        }
13858
13859        fn sample(param1: u8, param2: u8) {}
13860    "});
13861    handle_signature_help_request(&mut cx, mocked_response).await;
13862    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13863        .await;
13864    cx.editor(|editor, _, _| {
13865        assert!(editor.signature_help_state.is_shown());
13866    });
13867
13868    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13869    cx.update_editor(|editor, window, cx| {
13870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13871            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13872            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13873        })
13874    });
13875    cx.assert_editor_state(indoc! {"
13876        fn main() {
13877            sample(param1, ˇparam2);
13878        }
13879
13880        fn sample(param1: u8, param2: u8) {}
13881    "});
13882
13883    let mocked_response = lsp::SignatureHelp {
13884        signatures: vec![lsp::SignatureInformation {
13885            label: "fn sample(param1: u8, param2: u8)".to_string(),
13886            documentation: None,
13887            parameters: Some(vec![
13888                lsp::ParameterInformation {
13889                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13890                    documentation: None,
13891                },
13892                lsp::ParameterInformation {
13893                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13894                    documentation: None,
13895                },
13896            ]),
13897            active_parameter: None,
13898        }],
13899        active_signature: Some(0),
13900        active_parameter: Some(1),
13901    };
13902    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13903    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13904        .await;
13905    cx.update_editor(|editor, _, cx| {
13906        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13907    });
13908    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13909        .await;
13910    cx.update_editor(|editor, window, cx| {
13911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13912            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13913        })
13914    });
13915    cx.assert_editor_state(indoc! {"
13916        fn main() {
13917            sample(param1, «ˇparam2»);
13918        }
13919
13920        fn sample(param1: u8, param2: u8) {}
13921    "});
13922    cx.update_editor(|editor, window, cx| {
13923        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13924            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13925        })
13926    });
13927    cx.assert_editor_state(indoc! {"
13928        fn main() {
13929            sample(param1, ˇparam2);
13930        }
13931
13932        fn sample(param1: u8, param2: u8) {}
13933    "});
13934    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13935        .await;
13936}
13937
13938#[gpui::test]
13939async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13940    init_test(cx, |_| {});
13941
13942    let mut cx = EditorLspTestContext::new_rust(
13943        lsp::ServerCapabilities {
13944            signature_help_provider: Some(lsp::SignatureHelpOptions {
13945                ..Default::default()
13946            }),
13947            ..Default::default()
13948        },
13949        cx,
13950    )
13951    .await;
13952
13953    cx.set_state(indoc! {"
13954        fn main() {
13955            overloadedˇ
13956        }
13957    "});
13958
13959    cx.update_editor(|editor, window, cx| {
13960        editor.handle_input("(", window, cx);
13961        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13962    });
13963
13964    // Mock response with 3 signatures
13965    let mocked_response = lsp::SignatureHelp {
13966        signatures: vec![
13967            lsp::SignatureInformation {
13968                label: "fn overloaded(x: i32)".to_string(),
13969                documentation: None,
13970                parameters: Some(vec![lsp::ParameterInformation {
13971                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13972                    documentation: None,
13973                }]),
13974                active_parameter: None,
13975            },
13976            lsp::SignatureInformation {
13977                label: "fn overloaded(x: i32, y: i32)".to_string(),
13978                documentation: None,
13979                parameters: Some(vec![
13980                    lsp::ParameterInformation {
13981                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13982                        documentation: None,
13983                    },
13984                    lsp::ParameterInformation {
13985                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13986                        documentation: None,
13987                    },
13988                ]),
13989                active_parameter: None,
13990            },
13991            lsp::SignatureInformation {
13992                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13993                documentation: None,
13994                parameters: Some(vec![
13995                    lsp::ParameterInformation {
13996                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13997                        documentation: None,
13998                    },
13999                    lsp::ParameterInformation {
14000                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
14001                        documentation: None,
14002                    },
14003                    lsp::ParameterInformation {
14004                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
14005                        documentation: None,
14006                    },
14007                ]),
14008                active_parameter: None,
14009            },
14010        ],
14011        active_signature: Some(1),
14012        active_parameter: Some(0),
14013    };
14014    handle_signature_help_request(&mut cx, mocked_response).await;
14015
14016    cx.condition(|editor, _| editor.signature_help_state.is_shown())
14017        .await;
14018
14019    // Verify we have multiple signatures and the right one is selected
14020    cx.editor(|editor, _, _| {
14021        let popover = editor.signature_help_state.popover().cloned().unwrap();
14022        assert_eq!(popover.signatures.len(), 3);
14023        // active_signature was 1, so that should be the current
14024        assert_eq!(popover.current_signature, 1);
14025        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
14026        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
14027        assert_eq!(
14028            popover.signatures[2].label,
14029            "fn overloaded(x: i32, y: i32, z: i32)"
14030        );
14031    });
14032
14033    // Test navigation functionality
14034    cx.update_editor(|editor, window, cx| {
14035        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14036    });
14037
14038    cx.editor(|editor, _, _| {
14039        let popover = editor.signature_help_state.popover().cloned().unwrap();
14040        assert_eq!(popover.current_signature, 2);
14041    });
14042
14043    // Test wrap around
14044    cx.update_editor(|editor, window, cx| {
14045        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
14046    });
14047
14048    cx.editor(|editor, _, _| {
14049        let popover = editor.signature_help_state.popover().cloned().unwrap();
14050        assert_eq!(popover.current_signature, 0);
14051    });
14052
14053    // Test previous navigation
14054    cx.update_editor(|editor, window, cx| {
14055        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
14056    });
14057
14058    cx.editor(|editor, _, _| {
14059        let popover = editor.signature_help_state.popover().cloned().unwrap();
14060        assert_eq!(popover.current_signature, 2);
14061    });
14062}
14063
14064#[gpui::test]
14065async fn test_completion_mode(cx: &mut TestAppContext) {
14066    init_test(cx, |_| {});
14067    let mut cx = EditorLspTestContext::new_rust(
14068        lsp::ServerCapabilities {
14069            completion_provider: Some(lsp::CompletionOptions {
14070                resolve_provider: Some(true),
14071                ..Default::default()
14072            }),
14073            ..Default::default()
14074        },
14075        cx,
14076    )
14077    .await;
14078
14079    struct Run {
14080        run_description: &'static str,
14081        initial_state: String,
14082        buffer_marked_text: String,
14083        completion_label: &'static str,
14084        completion_text: &'static str,
14085        expected_with_insert_mode: String,
14086        expected_with_replace_mode: String,
14087        expected_with_replace_subsequence_mode: String,
14088        expected_with_replace_suffix_mode: String,
14089    }
14090
14091    let runs = [
14092        Run {
14093            run_description: "Start of word matches completion text",
14094            initial_state: "before ediˇ after".into(),
14095            buffer_marked_text: "before <edi|> after".into(),
14096            completion_label: "editor",
14097            completion_text: "editor",
14098            expected_with_insert_mode: "before editorˇ after".into(),
14099            expected_with_replace_mode: "before editorˇ after".into(),
14100            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14101            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14102        },
14103        Run {
14104            run_description: "Accept same text at the middle of the word",
14105            initial_state: "before ediˇtor after".into(),
14106            buffer_marked_text: "before <edi|tor> after".into(),
14107            completion_label: "editor",
14108            completion_text: "editor",
14109            expected_with_insert_mode: "before editorˇtor after".into(),
14110            expected_with_replace_mode: "before editorˇ after".into(),
14111            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14112            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14113        },
14114        Run {
14115            run_description: "End of word matches completion text -- cursor at end",
14116            initial_state: "before torˇ after".into(),
14117            buffer_marked_text: "before <tor|> after".into(),
14118            completion_label: "editor",
14119            completion_text: "editor",
14120            expected_with_insert_mode: "before editorˇ after".into(),
14121            expected_with_replace_mode: "before editorˇ after".into(),
14122            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14123            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14124        },
14125        Run {
14126            run_description: "End of word matches completion text -- cursor at start",
14127            initial_state: "before ˇtor after".into(),
14128            buffer_marked_text: "before <|tor> after".into(),
14129            completion_label: "editor",
14130            completion_text: "editor",
14131            expected_with_insert_mode: "before editorˇtor after".into(),
14132            expected_with_replace_mode: "before editorˇ after".into(),
14133            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14134            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14135        },
14136        Run {
14137            run_description: "Prepend text containing whitespace",
14138            initial_state: "pˇfield: bool".into(),
14139            buffer_marked_text: "<p|field>: bool".into(),
14140            completion_label: "pub ",
14141            completion_text: "pub ",
14142            expected_with_insert_mode: "pub ˇfield: bool".into(),
14143            expected_with_replace_mode: "pub ˇ: bool".into(),
14144            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14145            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14146        },
14147        Run {
14148            run_description: "Add element to start of list",
14149            initial_state: "[element_ˇelement_2]".into(),
14150            buffer_marked_text: "[<element_|element_2>]".into(),
14151            completion_label: "element_1",
14152            completion_text: "element_1",
14153            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14154            expected_with_replace_mode: "[element_1ˇ]".into(),
14155            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14156            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14157        },
14158        Run {
14159            run_description: "Add element to start of list -- first and second elements are equal",
14160            initial_state: "[elˇelement]".into(),
14161            buffer_marked_text: "[<el|element>]".into(),
14162            completion_label: "element",
14163            completion_text: "element",
14164            expected_with_insert_mode: "[elementˇelement]".into(),
14165            expected_with_replace_mode: "[elementˇ]".into(),
14166            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14167            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14168        },
14169        Run {
14170            run_description: "Ends with matching suffix",
14171            initial_state: "SubˇError".into(),
14172            buffer_marked_text: "<Sub|Error>".into(),
14173            completion_label: "SubscriptionError",
14174            completion_text: "SubscriptionError",
14175            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14176            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14177            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14178            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14179        },
14180        Run {
14181            run_description: "Suffix is a subsequence -- contiguous",
14182            initial_state: "SubˇErr".into(),
14183            buffer_marked_text: "<Sub|Err>".into(),
14184            completion_label: "SubscriptionError",
14185            completion_text: "SubscriptionError",
14186            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14187            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14188            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14189            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14190        },
14191        Run {
14192            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14193            initial_state: "Suˇscrirr".into(),
14194            buffer_marked_text: "<Su|scrirr>".into(),
14195            completion_label: "SubscriptionError",
14196            completion_text: "SubscriptionError",
14197            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14198            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14199            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14200            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14201        },
14202        Run {
14203            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14204            initial_state: "foo(indˇix)".into(),
14205            buffer_marked_text: "foo(<ind|ix>)".into(),
14206            completion_label: "node_index",
14207            completion_text: "node_index",
14208            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14209            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14210            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14211            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14212        },
14213        Run {
14214            run_description: "Replace range ends before cursor - should extend to cursor",
14215            initial_state: "before editˇo after".into(),
14216            buffer_marked_text: "before <{ed}>it|o after".into(),
14217            completion_label: "editor",
14218            completion_text: "editor",
14219            expected_with_insert_mode: "before editorˇo after".into(),
14220            expected_with_replace_mode: "before editorˇo after".into(),
14221            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14222            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14223        },
14224        Run {
14225            run_description: "Uses label for suffix matching",
14226            initial_state: "before ediˇtor after".into(),
14227            buffer_marked_text: "before <edi|tor> after".into(),
14228            completion_label: "editor",
14229            completion_text: "editor()",
14230            expected_with_insert_mode: "before editor()ˇtor after".into(),
14231            expected_with_replace_mode: "before editor()ˇ after".into(),
14232            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14233            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14234        },
14235        Run {
14236            run_description: "Case insensitive subsequence and suffix matching",
14237            initial_state: "before EDiˇtoR after".into(),
14238            buffer_marked_text: "before <EDi|toR> after".into(),
14239            completion_label: "editor",
14240            completion_text: "editor",
14241            expected_with_insert_mode: "before editorˇtoR after".into(),
14242            expected_with_replace_mode: "before editorˇ after".into(),
14243            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14244            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14245        },
14246    ];
14247
14248    for run in runs {
14249        let run_variations = [
14250            (LspInsertMode::Insert, run.expected_with_insert_mode),
14251            (LspInsertMode::Replace, run.expected_with_replace_mode),
14252            (
14253                LspInsertMode::ReplaceSubsequence,
14254                run.expected_with_replace_subsequence_mode,
14255            ),
14256            (
14257                LspInsertMode::ReplaceSuffix,
14258                run.expected_with_replace_suffix_mode,
14259            ),
14260        ];
14261
14262        for (lsp_insert_mode, expected_text) in run_variations {
14263            eprintln!(
14264                "run = {:?}, mode = {lsp_insert_mode:.?}",
14265                run.run_description,
14266            );
14267
14268            update_test_language_settings(&mut cx, |settings| {
14269                settings.defaults.completions = Some(CompletionSettingsContent {
14270                    lsp_insert_mode: Some(lsp_insert_mode),
14271                    words: Some(WordsCompletionMode::Disabled),
14272                    words_min_length: Some(0),
14273                    ..Default::default()
14274                });
14275            });
14276
14277            cx.set_state(&run.initial_state);
14278            cx.update_editor(|editor, window, cx| {
14279                editor.show_completions(&ShowCompletions, window, cx);
14280            });
14281
14282            let counter = Arc::new(AtomicUsize::new(0));
14283            handle_completion_request_with_insert_and_replace(
14284                &mut cx,
14285                &run.buffer_marked_text,
14286                vec![(run.completion_label, run.completion_text)],
14287                counter.clone(),
14288            )
14289            .await;
14290            cx.condition(|editor, _| editor.context_menu_visible())
14291                .await;
14292            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14293
14294            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14295                editor
14296                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14297                    .unwrap()
14298            });
14299            cx.assert_editor_state(&expected_text);
14300            handle_resolve_completion_request(&mut cx, None).await;
14301            apply_additional_edits.await.unwrap();
14302        }
14303    }
14304}
14305
14306#[gpui::test]
14307async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14308    init_test(cx, |_| {});
14309    let mut cx = EditorLspTestContext::new_rust(
14310        lsp::ServerCapabilities {
14311            completion_provider: Some(lsp::CompletionOptions {
14312                resolve_provider: Some(true),
14313                ..Default::default()
14314            }),
14315            ..Default::default()
14316        },
14317        cx,
14318    )
14319    .await;
14320
14321    let initial_state = "SubˇError";
14322    let buffer_marked_text = "<Sub|Error>";
14323    let completion_text = "SubscriptionError";
14324    let expected_with_insert_mode = "SubscriptionErrorˇError";
14325    let expected_with_replace_mode = "SubscriptionErrorˇ";
14326
14327    update_test_language_settings(&mut cx, |settings| {
14328        settings.defaults.completions = Some(CompletionSettingsContent {
14329            words: Some(WordsCompletionMode::Disabled),
14330            words_min_length: Some(0),
14331            // set the opposite here to ensure that the action is overriding the default behavior
14332            lsp_insert_mode: Some(LspInsertMode::Insert),
14333            ..Default::default()
14334        });
14335    });
14336
14337    cx.set_state(initial_state);
14338    cx.update_editor(|editor, window, cx| {
14339        editor.show_completions(&ShowCompletions, window, cx);
14340    });
14341
14342    let counter = Arc::new(AtomicUsize::new(0));
14343    handle_completion_request_with_insert_and_replace(
14344        &mut cx,
14345        buffer_marked_text,
14346        vec![(completion_text, completion_text)],
14347        counter.clone(),
14348    )
14349    .await;
14350    cx.condition(|editor, _| editor.context_menu_visible())
14351        .await;
14352    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14353
14354    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14355        editor
14356            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14357            .unwrap()
14358    });
14359    cx.assert_editor_state(expected_with_replace_mode);
14360    handle_resolve_completion_request(&mut cx, None).await;
14361    apply_additional_edits.await.unwrap();
14362
14363    update_test_language_settings(&mut cx, |settings| {
14364        settings.defaults.completions = Some(CompletionSettingsContent {
14365            words: Some(WordsCompletionMode::Disabled),
14366            words_min_length: Some(0),
14367            // set the opposite here to ensure that the action is overriding the default behavior
14368            lsp_insert_mode: Some(LspInsertMode::Replace),
14369            ..Default::default()
14370        });
14371    });
14372
14373    cx.set_state(initial_state);
14374    cx.update_editor(|editor, window, cx| {
14375        editor.show_completions(&ShowCompletions, window, cx);
14376    });
14377    handle_completion_request_with_insert_and_replace(
14378        &mut cx,
14379        buffer_marked_text,
14380        vec![(completion_text, completion_text)],
14381        counter.clone(),
14382    )
14383    .await;
14384    cx.condition(|editor, _| editor.context_menu_visible())
14385        .await;
14386    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14387
14388    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14389        editor
14390            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14391            .unwrap()
14392    });
14393    cx.assert_editor_state(expected_with_insert_mode);
14394    handle_resolve_completion_request(&mut cx, None).await;
14395    apply_additional_edits.await.unwrap();
14396}
14397
14398#[gpui::test]
14399async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14400    init_test(cx, |_| {});
14401    let mut cx = EditorLspTestContext::new_rust(
14402        lsp::ServerCapabilities {
14403            completion_provider: Some(lsp::CompletionOptions {
14404                resolve_provider: Some(true),
14405                ..Default::default()
14406            }),
14407            ..Default::default()
14408        },
14409        cx,
14410    )
14411    .await;
14412
14413    // scenario: surrounding text matches completion text
14414    let completion_text = "to_offset";
14415    let initial_state = indoc! {"
14416        1. buf.to_offˇsuffix
14417        2. buf.to_offˇsuf
14418        3. buf.to_offˇfix
14419        4. buf.to_offˇ
14420        5. into_offˇensive
14421        6. ˇsuffix
14422        7. let ˇ //
14423        8. aaˇzz
14424        9. buf.to_off«zzzzzˇ»suffix
14425        10. buf.«ˇzzzzz»suffix
14426        11. to_off«ˇzzzzz»
14427
14428        buf.to_offˇsuffix  // newest cursor
14429    "};
14430    let completion_marked_buffer = indoc! {"
14431        1. buf.to_offsuffix
14432        2. buf.to_offsuf
14433        3. buf.to_offfix
14434        4. buf.to_off
14435        5. into_offensive
14436        6. suffix
14437        7. let  //
14438        8. aazz
14439        9. buf.to_offzzzzzsuffix
14440        10. buf.zzzzzsuffix
14441        11. to_offzzzzz
14442
14443        buf.<to_off|suffix>  // newest cursor
14444    "};
14445    let expected = indoc! {"
14446        1. buf.to_offsetˇ
14447        2. buf.to_offsetˇsuf
14448        3. buf.to_offsetˇfix
14449        4. buf.to_offsetˇ
14450        5. into_offsetˇensive
14451        6. to_offsetˇsuffix
14452        7. let to_offsetˇ //
14453        8. aato_offsetˇzz
14454        9. buf.to_offsetˇ
14455        10. buf.to_offsetˇsuffix
14456        11. to_offsetˇ
14457
14458        buf.to_offsetˇ  // newest cursor
14459    "};
14460    cx.set_state(initial_state);
14461    cx.update_editor(|editor, window, cx| {
14462        editor.show_completions(&ShowCompletions, window, cx);
14463    });
14464    handle_completion_request_with_insert_and_replace(
14465        &mut cx,
14466        completion_marked_buffer,
14467        vec![(completion_text, completion_text)],
14468        Arc::new(AtomicUsize::new(0)),
14469    )
14470    .await;
14471    cx.condition(|editor, _| editor.context_menu_visible())
14472        .await;
14473    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14474        editor
14475            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14476            .unwrap()
14477    });
14478    cx.assert_editor_state(expected);
14479    handle_resolve_completion_request(&mut cx, None).await;
14480    apply_additional_edits.await.unwrap();
14481
14482    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14483    let completion_text = "foo_and_bar";
14484    let initial_state = indoc! {"
14485        1. ooanbˇ
14486        2. zooanbˇ
14487        3. ooanbˇz
14488        4. zooanbˇz
14489        5. ooanˇ
14490        6. oanbˇ
14491
14492        ooanbˇ
14493    "};
14494    let completion_marked_buffer = indoc! {"
14495        1. ooanb
14496        2. zooanb
14497        3. ooanbz
14498        4. zooanbz
14499        5. ooan
14500        6. oanb
14501
14502        <ooanb|>
14503    "};
14504    let expected = indoc! {"
14505        1. foo_and_barˇ
14506        2. zfoo_and_barˇ
14507        3. foo_and_barˇz
14508        4. zfoo_and_barˇz
14509        5. ooanfoo_and_barˇ
14510        6. oanbfoo_and_barˇ
14511
14512        foo_and_barˇ
14513    "};
14514    cx.set_state(initial_state);
14515    cx.update_editor(|editor, window, cx| {
14516        editor.show_completions(&ShowCompletions, window, cx);
14517    });
14518    handle_completion_request_with_insert_and_replace(
14519        &mut cx,
14520        completion_marked_buffer,
14521        vec![(completion_text, completion_text)],
14522        Arc::new(AtomicUsize::new(0)),
14523    )
14524    .await;
14525    cx.condition(|editor, _| editor.context_menu_visible())
14526        .await;
14527    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14528        editor
14529            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14530            .unwrap()
14531    });
14532    cx.assert_editor_state(expected);
14533    handle_resolve_completion_request(&mut cx, None).await;
14534    apply_additional_edits.await.unwrap();
14535
14536    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14537    // (expects the same as if it was inserted at the end)
14538    let completion_text = "foo_and_bar";
14539    let initial_state = indoc! {"
14540        1. ooˇanb
14541        2. zooˇanb
14542        3. ooˇanbz
14543        4. zooˇanbz
14544
14545        ooˇanb
14546    "};
14547    let completion_marked_buffer = indoc! {"
14548        1. ooanb
14549        2. zooanb
14550        3. ooanbz
14551        4. zooanbz
14552
14553        <oo|anb>
14554    "};
14555    let expected = indoc! {"
14556        1. foo_and_barˇ
14557        2. zfoo_and_barˇ
14558        3. foo_and_barˇz
14559        4. zfoo_and_barˇz
14560
14561        foo_and_barˇ
14562    "};
14563    cx.set_state(initial_state);
14564    cx.update_editor(|editor, window, cx| {
14565        editor.show_completions(&ShowCompletions, window, cx);
14566    });
14567    handle_completion_request_with_insert_and_replace(
14568        &mut cx,
14569        completion_marked_buffer,
14570        vec![(completion_text, completion_text)],
14571        Arc::new(AtomicUsize::new(0)),
14572    )
14573    .await;
14574    cx.condition(|editor, _| editor.context_menu_visible())
14575        .await;
14576    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14577        editor
14578            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14579            .unwrap()
14580    });
14581    cx.assert_editor_state(expected);
14582    handle_resolve_completion_request(&mut cx, None).await;
14583    apply_additional_edits.await.unwrap();
14584}
14585
14586// This used to crash
14587#[gpui::test]
14588async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14589    init_test(cx, |_| {});
14590
14591    let buffer_text = indoc! {"
14592        fn main() {
14593            10.satu;
14594
14595            //
14596            // separate cursors so they open in different excerpts (manually reproducible)
14597            //
14598
14599            10.satu20;
14600        }
14601    "};
14602    let multibuffer_text_with_selections = indoc! {"
14603        fn main() {
14604            10.satuˇ;
14605
14606            //
14607
14608            //
14609
14610            10.satuˇ20;
14611        }
14612    "};
14613    let expected_multibuffer = indoc! {"
14614        fn main() {
14615            10.saturating_sub()ˇ;
14616
14617            //
14618
14619            //
14620
14621            10.saturating_sub()ˇ;
14622        }
14623    "};
14624
14625    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14626    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14627
14628    let fs = FakeFs::new(cx.executor());
14629    fs.insert_tree(
14630        path!("/a"),
14631        json!({
14632            "main.rs": buffer_text,
14633        }),
14634    )
14635    .await;
14636
14637    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14638    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14639    language_registry.add(rust_lang());
14640    let mut fake_servers = language_registry.register_fake_lsp(
14641        "Rust",
14642        FakeLspAdapter {
14643            capabilities: lsp::ServerCapabilities {
14644                completion_provider: Some(lsp::CompletionOptions {
14645                    resolve_provider: None,
14646                    ..lsp::CompletionOptions::default()
14647                }),
14648                ..lsp::ServerCapabilities::default()
14649            },
14650            ..FakeLspAdapter::default()
14651        },
14652    );
14653    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14654    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14655    let buffer = project
14656        .update(cx, |project, cx| {
14657            project.open_local_buffer(path!("/a/main.rs"), cx)
14658        })
14659        .await
14660        .unwrap();
14661
14662    let multi_buffer = cx.new(|cx| {
14663        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14664        multi_buffer.push_excerpts(
14665            buffer.clone(),
14666            [ExcerptRange::new(0..first_excerpt_end)],
14667            cx,
14668        );
14669        multi_buffer.push_excerpts(
14670            buffer.clone(),
14671            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14672            cx,
14673        );
14674        multi_buffer
14675    });
14676
14677    let editor = workspace
14678        .update(cx, |_, window, cx| {
14679            cx.new(|cx| {
14680                Editor::new(
14681                    EditorMode::Full {
14682                        scale_ui_elements_with_buffer_font_size: false,
14683                        show_active_line_background: false,
14684                        sizing_behavior: SizingBehavior::Default,
14685                    },
14686                    multi_buffer.clone(),
14687                    Some(project.clone()),
14688                    window,
14689                    cx,
14690                )
14691            })
14692        })
14693        .unwrap();
14694
14695    let pane = workspace
14696        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14697        .unwrap();
14698    pane.update_in(cx, |pane, window, cx| {
14699        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14700    });
14701
14702    let fake_server = fake_servers.next().await.unwrap();
14703
14704    editor.update_in(cx, |editor, window, cx| {
14705        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14706            s.select_ranges([
14707                Point::new(1, 11)..Point::new(1, 11),
14708                Point::new(7, 11)..Point::new(7, 11),
14709            ])
14710        });
14711
14712        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14713    });
14714
14715    editor.update_in(cx, |editor, window, cx| {
14716        editor.show_completions(&ShowCompletions, window, cx);
14717    });
14718
14719    fake_server
14720        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14721            let completion_item = lsp::CompletionItem {
14722                label: "saturating_sub()".into(),
14723                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14724                    lsp::InsertReplaceEdit {
14725                        new_text: "saturating_sub()".to_owned(),
14726                        insert: lsp::Range::new(
14727                            lsp::Position::new(7, 7),
14728                            lsp::Position::new(7, 11),
14729                        ),
14730                        replace: lsp::Range::new(
14731                            lsp::Position::new(7, 7),
14732                            lsp::Position::new(7, 13),
14733                        ),
14734                    },
14735                )),
14736                ..lsp::CompletionItem::default()
14737            };
14738
14739            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14740        })
14741        .next()
14742        .await
14743        .unwrap();
14744
14745    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14746        .await;
14747
14748    editor
14749        .update_in(cx, |editor, window, cx| {
14750            editor
14751                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14752                .unwrap()
14753        })
14754        .await
14755        .unwrap();
14756
14757    editor.update(cx, |editor, cx| {
14758        assert_text_with_selections(editor, expected_multibuffer, cx);
14759    })
14760}
14761
14762#[gpui::test]
14763async fn test_completion(cx: &mut TestAppContext) {
14764    init_test(cx, |_| {});
14765
14766    let mut cx = EditorLspTestContext::new_rust(
14767        lsp::ServerCapabilities {
14768            completion_provider: Some(lsp::CompletionOptions {
14769                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14770                resolve_provider: Some(true),
14771                ..Default::default()
14772            }),
14773            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14774            ..Default::default()
14775        },
14776        cx,
14777    )
14778    .await;
14779    let counter = Arc::new(AtomicUsize::new(0));
14780
14781    cx.set_state(indoc! {"
14782        oneˇ
14783        two
14784        three
14785    "});
14786    cx.simulate_keystroke(".");
14787    handle_completion_request(
14788        indoc! {"
14789            one.|<>
14790            two
14791            three
14792        "},
14793        vec!["first_completion", "second_completion"],
14794        true,
14795        counter.clone(),
14796        &mut cx,
14797    )
14798    .await;
14799    cx.condition(|editor, _| editor.context_menu_visible())
14800        .await;
14801    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14802
14803    let _handler = handle_signature_help_request(
14804        &mut cx,
14805        lsp::SignatureHelp {
14806            signatures: vec![lsp::SignatureInformation {
14807                label: "test signature".to_string(),
14808                documentation: None,
14809                parameters: Some(vec![lsp::ParameterInformation {
14810                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14811                    documentation: None,
14812                }]),
14813                active_parameter: None,
14814            }],
14815            active_signature: None,
14816            active_parameter: None,
14817        },
14818    );
14819    cx.update_editor(|editor, window, cx| {
14820        assert!(
14821            !editor.signature_help_state.is_shown(),
14822            "No signature help was called for"
14823        );
14824        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14825    });
14826    cx.run_until_parked();
14827    cx.update_editor(|editor, _, _| {
14828        assert!(
14829            !editor.signature_help_state.is_shown(),
14830            "No signature help should be shown when completions menu is open"
14831        );
14832    });
14833
14834    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14835        editor.context_menu_next(&Default::default(), window, cx);
14836        editor
14837            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14838            .unwrap()
14839    });
14840    cx.assert_editor_state(indoc! {"
14841        one.second_completionˇ
14842        two
14843        three
14844    "});
14845
14846    handle_resolve_completion_request(
14847        &mut cx,
14848        Some(vec![
14849            (
14850                //This overlaps with the primary completion edit which is
14851                //misbehavior from the LSP spec, test that we filter it out
14852                indoc! {"
14853                    one.second_ˇcompletion
14854                    two
14855                    threeˇ
14856                "},
14857                "overlapping additional edit",
14858            ),
14859            (
14860                indoc! {"
14861                    one.second_completion
14862                    two
14863                    threeˇ
14864                "},
14865                "\nadditional edit",
14866            ),
14867        ]),
14868    )
14869    .await;
14870    apply_additional_edits.await.unwrap();
14871    cx.assert_editor_state(indoc! {"
14872        one.second_completionˇ
14873        two
14874        three
14875        additional edit
14876    "});
14877
14878    cx.set_state(indoc! {"
14879        one.second_completion
14880        twoˇ
14881        threeˇ
14882        additional edit
14883    "});
14884    cx.simulate_keystroke(" ");
14885    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14886    cx.simulate_keystroke("s");
14887    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14888
14889    cx.assert_editor_state(indoc! {"
14890        one.second_completion
14891        two sˇ
14892        three sˇ
14893        additional edit
14894    "});
14895    handle_completion_request(
14896        indoc! {"
14897            one.second_completion
14898            two s
14899            three <s|>
14900            additional edit
14901        "},
14902        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14903        true,
14904        counter.clone(),
14905        &mut cx,
14906    )
14907    .await;
14908    cx.condition(|editor, _| editor.context_menu_visible())
14909        .await;
14910    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14911
14912    cx.simulate_keystroke("i");
14913
14914    handle_completion_request(
14915        indoc! {"
14916            one.second_completion
14917            two si
14918            three <si|>
14919            additional edit
14920        "},
14921        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14922        true,
14923        counter.clone(),
14924        &mut cx,
14925    )
14926    .await;
14927    cx.condition(|editor, _| editor.context_menu_visible())
14928        .await;
14929    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14930
14931    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14932        editor
14933            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14934            .unwrap()
14935    });
14936    cx.assert_editor_state(indoc! {"
14937        one.second_completion
14938        two sixth_completionˇ
14939        three sixth_completionˇ
14940        additional edit
14941    "});
14942
14943    apply_additional_edits.await.unwrap();
14944
14945    update_test_language_settings(&mut cx, |settings| {
14946        settings.defaults.show_completions_on_input = Some(false);
14947    });
14948    cx.set_state("editorˇ");
14949    cx.simulate_keystroke(".");
14950    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14951    cx.simulate_keystrokes("c l o");
14952    cx.assert_editor_state("editor.cloˇ");
14953    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14954    cx.update_editor(|editor, window, cx| {
14955        editor.show_completions(&ShowCompletions, window, cx);
14956    });
14957    handle_completion_request(
14958        "editor.<clo|>",
14959        vec!["close", "clobber"],
14960        true,
14961        counter.clone(),
14962        &mut cx,
14963    )
14964    .await;
14965    cx.condition(|editor, _| editor.context_menu_visible())
14966        .await;
14967    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14968
14969    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14970        editor
14971            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14972            .unwrap()
14973    });
14974    cx.assert_editor_state("editor.clobberˇ");
14975    handle_resolve_completion_request(&mut cx, None).await;
14976    apply_additional_edits.await.unwrap();
14977}
14978
14979#[gpui::test]
14980async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
14981    init_test(cx, |_| {});
14982
14983    let fs = FakeFs::new(cx.executor());
14984    fs.insert_tree(
14985        path!("/a"),
14986        json!({
14987            "main.rs": "",
14988        }),
14989    )
14990    .await;
14991
14992    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14993    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14994    language_registry.add(rust_lang());
14995    let command_calls = Arc::new(AtomicUsize::new(0));
14996    let registered_command = "_the/command";
14997
14998    let closure_command_calls = command_calls.clone();
14999    let mut fake_servers = language_registry.register_fake_lsp(
15000        "Rust",
15001        FakeLspAdapter {
15002            capabilities: lsp::ServerCapabilities {
15003                completion_provider: Some(lsp::CompletionOptions {
15004                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15005                    ..lsp::CompletionOptions::default()
15006                }),
15007                execute_command_provider: Some(lsp::ExecuteCommandOptions {
15008                    commands: vec![registered_command.to_owned()],
15009                    ..lsp::ExecuteCommandOptions::default()
15010                }),
15011                ..lsp::ServerCapabilities::default()
15012            },
15013            initializer: Some(Box::new(move |fake_server| {
15014                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15015                    move |params, _| async move {
15016                        Ok(Some(lsp::CompletionResponse::Array(vec![
15017                            lsp::CompletionItem {
15018                                label: "registered_command".to_owned(),
15019                                text_edit: gen_text_edit(&params, ""),
15020                                command: Some(lsp::Command {
15021                                    title: registered_command.to_owned(),
15022                                    command: "_the/command".to_owned(),
15023                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
15024                                }),
15025                                ..lsp::CompletionItem::default()
15026                            },
15027                            lsp::CompletionItem {
15028                                label: "unregistered_command".to_owned(),
15029                                text_edit: gen_text_edit(&params, ""),
15030                                command: Some(lsp::Command {
15031                                    title: "????????????".to_owned(),
15032                                    command: "????????????".to_owned(),
15033                                    arguments: Some(vec![serde_json::Value::Null]),
15034                                }),
15035                                ..lsp::CompletionItem::default()
15036                            },
15037                        ])))
15038                    },
15039                );
15040                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
15041                    let command_calls = closure_command_calls.clone();
15042                    move |params, _| {
15043                        assert_eq!(params.command, registered_command);
15044                        let command_calls = command_calls.clone();
15045                        async move {
15046                            command_calls.fetch_add(1, atomic::Ordering::Release);
15047                            Ok(Some(json!(null)))
15048                        }
15049                    }
15050                });
15051            })),
15052            ..FakeLspAdapter::default()
15053        },
15054    );
15055    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15056    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15057    let editor = workspace
15058        .update(cx, |workspace, window, cx| {
15059            workspace.open_abs_path(
15060                PathBuf::from(path!("/a/main.rs")),
15061                OpenOptions::default(),
15062                window,
15063                cx,
15064            )
15065        })
15066        .unwrap()
15067        .await
15068        .unwrap()
15069        .downcast::<Editor>()
15070        .unwrap();
15071    let _fake_server = fake_servers.next().await.unwrap();
15072
15073    editor.update_in(cx, |editor, window, cx| {
15074        cx.focus_self(window);
15075        editor.move_to_end(&MoveToEnd, window, cx);
15076        editor.handle_input(".", window, cx);
15077    });
15078    cx.run_until_parked();
15079    editor.update(cx, |editor, _| {
15080        assert!(editor.context_menu_visible());
15081        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15082        {
15083            let completion_labels = menu
15084                .completions
15085                .borrow()
15086                .iter()
15087                .map(|c| c.label.text.clone())
15088                .collect::<Vec<_>>();
15089            assert_eq!(
15090                completion_labels,
15091                &["registered_command", "unregistered_command",],
15092            );
15093        } else {
15094            panic!("expected completion menu to be open");
15095        }
15096    });
15097
15098    editor
15099        .update_in(cx, |editor, window, cx| {
15100            editor
15101                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15102                .unwrap()
15103        })
15104        .await
15105        .unwrap();
15106    cx.run_until_parked();
15107    assert_eq!(
15108        command_calls.load(atomic::Ordering::Acquire),
15109        1,
15110        "For completion with a registered command, Zed should send a command execution request",
15111    );
15112
15113    editor.update_in(cx, |editor, window, cx| {
15114        cx.focus_self(window);
15115        editor.handle_input(".", window, cx);
15116    });
15117    cx.run_until_parked();
15118    editor.update(cx, |editor, _| {
15119        assert!(editor.context_menu_visible());
15120        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15121        {
15122            let completion_labels = menu
15123                .completions
15124                .borrow()
15125                .iter()
15126                .map(|c| c.label.text.clone())
15127                .collect::<Vec<_>>();
15128            assert_eq!(
15129                completion_labels,
15130                &["registered_command", "unregistered_command",],
15131            );
15132        } else {
15133            panic!("expected completion menu to be open");
15134        }
15135    });
15136    editor
15137        .update_in(cx, |editor, window, cx| {
15138            editor.context_menu_next(&Default::default(), window, cx);
15139            editor
15140                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15141                .unwrap()
15142        })
15143        .await
15144        .unwrap();
15145    cx.run_until_parked();
15146    assert_eq!(
15147        command_calls.load(atomic::Ordering::Acquire),
15148        1,
15149        "For completion with an unregistered command, Zed should not send a command execution request",
15150    );
15151}
15152
15153#[gpui::test]
15154async fn test_completion_reuse(cx: &mut TestAppContext) {
15155    init_test(cx, |_| {});
15156
15157    let mut cx = EditorLspTestContext::new_rust(
15158        lsp::ServerCapabilities {
15159            completion_provider: Some(lsp::CompletionOptions {
15160                trigger_characters: Some(vec![".".to_string()]),
15161                ..Default::default()
15162            }),
15163            ..Default::default()
15164        },
15165        cx,
15166    )
15167    .await;
15168
15169    let counter = Arc::new(AtomicUsize::new(0));
15170    cx.set_state("objˇ");
15171    cx.simulate_keystroke(".");
15172
15173    // Initial completion request returns complete results
15174    let is_incomplete = false;
15175    handle_completion_request(
15176        "obj.|<>",
15177        vec!["a", "ab", "abc"],
15178        is_incomplete,
15179        counter.clone(),
15180        &mut cx,
15181    )
15182    .await;
15183    cx.run_until_parked();
15184    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15185    cx.assert_editor_state("obj.ˇ");
15186    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15187
15188    // Type "a" - filters existing completions
15189    cx.simulate_keystroke("a");
15190    cx.run_until_parked();
15191    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15192    cx.assert_editor_state("obj.aˇ");
15193    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15194
15195    // Type "b" - filters existing completions
15196    cx.simulate_keystroke("b");
15197    cx.run_until_parked();
15198    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15199    cx.assert_editor_state("obj.abˇ");
15200    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15201
15202    // Type "c" - filters existing completions
15203    cx.simulate_keystroke("c");
15204    cx.run_until_parked();
15205    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15206    cx.assert_editor_state("obj.abcˇ");
15207    check_displayed_completions(vec!["abc"], &mut cx);
15208
15209    // Backspace to delete "c" - filters existing completions
15210    cx.update_editor(|editor, window, cx| {
15211        editor.backspace(&Backspace, window, cx);
15212    });
15213    cx.run_until_parked();
15214    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15215    cx.assert_editor_state("obj.abˇ");
15216    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15217
15218    // Moving cursor to the left dismisses menu.
15219    cx.update_editor(|editor, window, cx| {
15220        editor.move_left(&MoveLeft, window, cx);
15221    });
15222    cx.run_until_parked();
15223    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15224    cx.assert_editor_state("obj.aˇb");
15225    cx.update_editor(|editor, _, _| {
15226        assert_eq!(editor.context_menu_visible(), false);
15227    });
15228
15229    // Type "b" - new request
15230    cx.simulate_keystroke("b");
15231    let is_incomplete = false;
15232    handle_completion_request(
15233        "obj.<ab|>a",
15234        vec!["ab", "abc"],
15235        is_incomplete,
15236        counter.clone(),
15237        &mut cx,
15238    )
15239    .await;
15240    cx.run_until_parked();
15241    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15242    cx.assert_editor_state("obj.abˇb");
15243    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15244
15245    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15246    cx.update_editor(|editor, window, cx| {
15247        editor.backspace(&Backspace, window, cx);
15248    });
15249    let is_incomplete = false;
15250    handle_completion_request(
15251        "obj.<a|>b",
15252        vec!["a", "ab", "abc"],
15253        is_incomplete,
15254        counter.clone(),
15255        &mut cx,
15256    )
15257    .await;
15258    cx.run_until_parked();
15259    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15260    cx.assert_editor_state("obj.aˇb");
15261    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15262
15263    // Backspace to delete "a" - dismisses menu.
15264    cx.update_editor(|editor, window, cx| {
15265        editor.backspace(&Backspace, window, cx);
15266    });
15267    cx.run_until_parked();
15268    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15269    cx.assert_editor_state("obj.ˇb");
15270    cx.update_editor(|editor, _, _| {
15271        assert_eq!(editor.context_menu_visible(), false);
15272    });
15273}
15274
15275#[gpui::test]
15276async fn test_word_completion(cx: &mut TestAppContext) {
15277    let lsp_fetch_timeout_ms = 10;
15278    init_test(cx, |language_settings| {
15279        language_settings.defaults.completions = Some(CompletionSettingsContent {
15280            words_min_length: Some(0),
15281            lsp_fetch_timeout_ms: Some(10),
15282            lsp_insert_mode: Some(LspInsertMode::Insert),
15283            ..Default::default()
15284        });
15285    });
15286
15287    let mut cx = EditorLspTestContext::new_rust(
15288        lsp::ServerCapabilities {
15289            completion_provider: Some(lsp::CompletionOptions {
15290                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15291                ..lsp::CompletionOptions::default()
15292            }),
15293            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15294            ..lsp::ServerCapabilities::default()
15295        },
15296        cx,
15297    )
15298    .await;
15299
15300    let throttle_completions = Arc::new(AtomicBool::new(false));
15301
15302    let lsp_throttle_completions = throttle_completions.clone();
15303    let _completion_requests_handler =
15304        cx.lsp
15305            .server
15306            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15307                let lsp_throttle_completions = lsp_throttle_completions.clone();
15308                let cx = cx.clone();
15309                async move {
15310                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15311                        cx.background_executor()
15312                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15313                            .await;
15314                    }
15315                    Ok(Some(lsp::CompletionResponse::Array(vec![
15316                        lsp::CompletionItem {
15317                            label: "first".into(),
15318                            ..lsp::CompletionItem::default()
15319                        },
15320                        lsp::CompletionItem {
15321                            label: "last".into(),
15322                            ..lsp::CompletionItem::default()
15323                        },
15324                    ])))
15325                }
15326            });
15327
15328    cx.set_state(indoc! {"
15329        oneˇ
15330        two
15331        three
15332    "});
15333    cx.simulate_keystroke(".");
15334    cx.executor().run_until_parked();
15335    cx.condition(|editor, _| editor.context_menu_visible())
15336        .await;
15337    cx.update_editor(|editor, window, cx| {
15338        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15339        {
15340            assert_eq!(
15341                completion_menu_entries(menu),
15342                &["first", "last"],
15343                "When LSP server is fast to reply, no fallback word completions are used"
15344            );
15345        } else {
15346            panic!("expected completion menu to be open");
15347        }
15348        editor.cancel(&Cancel, window, cx);
15349    });
15350    cx.executor().run_until_parked();
15351    cx.condition(|editor, _| !editor.context_menu_visible())
15352        .await;
15353
15354    throttle_completions.store(true, atomic::Ordering::Release);
15355    cx.simulate_keystroke(".");
15356    cx.executor()
15357        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15358    cx.executor().run_until_parked();
15359    cx.condition(|editor, _| editor.context_menu_visible())
15360        .await;
15361    cx.update_editor(|editor, _, _| {
15362        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15363        {
15364            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15365                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15366        } else {
15367            panic!("expected completion menu to be open");
15368        }
15369    });
15370}
15371
15372#[gpui::test]
15373async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15374    init_test(cx, |language_settings| {
15375        language_settings.defaults.completions = Some(CompletionSettingsContent {
15376            words: Some(WordsCompletionMode::Enabled),
15377            words_min_length: Some(0),
15378            lsp_insert_mode: Some(LspInsertMode::Insert),
15379            ..Default::default()
15380        });
15381    });
15382
15383    let mut cx = EditorLspTestContext::new_rust(
15384        lsp::ServerCapabilities {
15385            completion_provider: Some(lsp::CompletionOptions {
15386                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15387                ..lsp::CompletionOptions::default()
15388            }),
15389            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15390            ..lsp::ServerCapabilities::default()
15391        },
15392        cx,
15393    )
15394    .await;
15395
15396    let _completion_requests_handler =
15397        cx.lsp
15398            .server
15399            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15400                Ok(Some(lsp::CompletionResponse::Array(vec![
15401                    lsp::CompletionItem {
15402                        label: "first".into(),
15403                        ..lsp::CompletionItem::default()
15404                    },
15405                    lsp::CompletionItem {
15406                        label: "last".into(),
15407                        ..lsp::CompletionItem::default()
15408                    },
15409                ])))
15410            });
15411
15412    cx.set_state(indoc! {"ˇ
15413        first
15414        last
15415        second
15416    "});
15417    cx.simulate_keystroke(".");
15418    cx.executor().run_until_parked();
15419    cx.condition(|editor, _| editor.context_menu_visible())
15420        .await;
15421    cx.update_editor(|editor, _, _| {
15422        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15423        {
15424            assert_eq!(
15425                completion_menu_entries(menu),
15426                &["first", "last", "second"],
15427                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15428            );
15429        } else {
15430            panic!("expected completion menu to be open");
15431        }
15432    });
15433}
15434
15435#[gpui::test]
15436async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15437    init_test(cx, |language_settings| {
15438        language_settings.defaults.completions = Some(CompletionSettingsContent {
15439            words: Some(WordsCompletionMode::Disabled),
15440            words_min_length: Some(0),
15441            lsp_insert_mode: Some(LspInsertMode::Insert),
15442            ..Default::default()
15443        });
15444    });
15445
15446    let mut cx = EditorLspTestContext::new_rust(
15447        lsp::ServerCapabilities {
15448            completion_provider: Some(lsp::CompletionOptions {
15449                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15450                ..lsp::CompletionOptions::default()
15451            }),
15452            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15453            ..lsp::ServerCapabilities::default()
15454        },
15455        cx,
15456    )
15457    .await;
15458
15459    let _completion_requests_handler =
15460        cx.lsp
15461            .server
15462            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15463                panic!("LSP completions should not be queried when dealing with word completions")
15464            });
15465
15466    cx.set_state(indoc! {"ˇ
15467        first
15468        last
15469        second
15470    "});
15471    cx.update_editor(|editor, window, cx| {
15472        editor.show_word_completions(&ShowWordCompletions, window, cx);
15473    });
15474    cx.executor().run_until_parked();
15475    cx.condition(|editor, _| editor.context_menu_visible())
15476        .await;
15477    cx.update_editor(|editor, _, _| {
15478        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15479        {
15480            assert_eq!(
15481                completion_menu_entries(menu),
15482                &["first", "last", "second"],
15483                "`ShowWordCompletions` action should show word completions"
15484            );
15485        } else {
15486            panic!("expected completion menu to be open");
15487        }
15488    });
15489
15490    cx.simulate_keystroke("l");
15491    cx.executor().run_until_parked();
15492    cx.condition(|editor, _| editor.context_menu_visible())
15493        .await;
15494    cx.update_editor(|editor, _, _| {
15495        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15496        {
15497            assert_eq!(
15498                completion_menu_entries(menu),
15499                &["last"],
15500                "After showing word completions, further editing should filter them and not query the LSP"
15501            );
15502        } else {
15503            panic!("expected completion menu to be open");
15504        }
15505    });
15506}
15507
15508#[gpui::test]
15509async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15510    init_test(cx, |language_settings| {
15511        language_settings.defaults.completions = Some(CompletionSettingsContent {
15512            words_min_length: Some(0),
15513            lsp: Some(false),
15514            lsp_insert_mode: Some(LspInsertMode::Insert),
15515            ..Default::default()
15516        });
15517    });
15518
15519    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15520
15521    cx.set_state(indoc! {"ˇ
15522        0_usize
15523        let
15524        33
15525        4.5f32
15526    "});
15527    cx.update_editor(|editor, window, cx| {
15528        editor.show_completions(&ShowCompletions, window, cx);
15529    });
15530    cx.executor().run_until_parked();
15531    cx.condition(|editor, _| editor.context_menu_visible())
15532        .await;
15533    cx.update_editor(|editor, window, cx| {
15534        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15535        {
15536            assert_eq!(
15537                completion_menu_entries(menu),
15538                &["let"],
15539                "With no digits in the completion query, no digits should be in the word completions"
15540            );
15541        } else {
15542            panic!("expected completion menu to be open");
15543        }
15544        editor.cancel(&Cancel, window, cx);
15545    });
15546
15547    cx.set_state(indoc! {"15548        0_usize
15549        let
15550        3
15551        33.35f32
15552    "});
15553    cx.update_editor(|editor, window, cx| {
15554        editor.show_completions(&ShowCompletions, window, cx);
15555    });
15556    cx.executor().run_until_parked();
15557    cx.condition(|editor, _| editor.context_menu_visible())
15558        .await;
15559    cx.update_editor(|editor, _, _| {
15560        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15561        {
15562            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15563                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15564        } else {
15565            panic!("expected completion menu to be open");
15566        }
15567    });
15568}
15569
15570#[gpui::test]
15571async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15572    init_test(cx, |language_settings| {
15573        language_settings.defaults.completions = Some(CompletionSettingsContent {
15574            words: Some(WordsCompletionMode::Enabled),
15575            words_min_length: Some(3),
15576            lsp_insert_mode: Some(LspInsertMode::Insert),
15577            ..Default::default()
15578        });
15579    });
15580
15581    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15582    cx.set_state(indoc! {"ˇ
15583        wow
15584        wowen
15585        wowser
15586    "});
15587    cx.simulate_keystroke("w");
15588    cx.executor().run_until_parked();
15589    cx.update_editor(|editor, _, _| {
15590        if editor.context_menu.borrow_mut().is_some() {
15591            panic!(
15592                "expected completion menu to be hidden, as words completion threshold is not met"
15593            );
15594        }
15595    });
15596
15597    cx.update_editor(|editor, window, cx| {
15598        editor.show_word_completions(&ShowWordCompletions, window, cx);
15599    });
15600    cx.executor().run_until_parked();
15601    cx.update_editor(|editor, window, cx| {
15602        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15603        {
15604            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");
15605        } else {
15606            panic!("expected completion menu to be open after the word completions are called with an action");
15607        }
15608
15609        editor.cancel(&Cancel, window, cx);
15610    });
15611    cx.update_editor(|editor, _, _| {
15612        if editor.context_menu.borrow_mut().is_some() {
15613            panic!("expected completion menu to be hidden after canceling");
15614        }
15615    });
15616
15617    cx.simulate_keystroke("o");
15618    cx.executor().run_until_parked();
15619    cx.update_editor(|editor, _, _| {
15620        if editor.context_menu.borrow_mut().is_some() {
15621            panic!(
15622                "expected completion menu to be hidden, as words completion threshold is not met still"
15623            );
15624        }
15625    });
15626
15627    cx.simulate_keystroke("w");
15628    cx.executor().run_until_parked();
15629    cx.update_editor(|editor, _, _| {
15630        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15631        {
15632            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15633        } else {
15634            panic!("expected completion menu to be open after the word completions threshold is met");
15635        }
15636    });
15637}
15638
15639#[gpui::test]
15640async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15641    init_test(cx, |language_settings| {
15642        language_settings.defaults.completions = Some(CompletionSettingsContent {
15643            words: Some(WordsCompletionMode::Enabled),
15644            words_min_length: Some(0),
15645            lsp_insert_mode: Some(LspInsertMode::Insert),
15646            ..Default::default()
15647        });
15648    });
15649
15650    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15651    cx.update_editor(|editor, _, _| {
15652        editor.disable_word_completions();
15653    });
15654    cx.set_state(indoc! {"ˇ
15655        wow
15656        wowen
15657        wowser
15658    "});
15659    cx.simulate_keystroke("w");
15660    cx.executor().run_until_parked();
15661    cx.update_editor(|editor, _, _| {
15662        if editor.context_menu.borrow_mut().is_some() {
15663            panic!(
15664                "expected completion menu to be hidden, as words completion are disabled for this editor"
15665            );
15666        }
15667    });
15668
15669    cx.update_editor(|editor, window, cx| {
15670        editor.show_word_completions(&ShowWordCompletions, window, cx);
15671    });
15672    cx.executor().run_until_parked();
15673    cx.update_editor(|editor, _, _| {
15674        if editor.context_menu.borrow_mut().is_some() {
15675            panic!(
15676                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15677            );
15678        }
15679    });
15680}
15681
15682#[gpui::test]
15683async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15684    init_test(cx, |language_settings| {
15685        language_settings.defaults.completions = Some(CompletionSettingsContent {
15686            words: Some(WordsCompletionMode::Disabled),
15687            words_min_length: Some(0),
15688            lsp_insert_mode: Some(LspInsertMode::Insert),
15689            ..Default::default()
15690        });
15691    });
15692
15693    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15694    cx.update_editor(|editor, _, _| {
15695        editor.set_completion_provider(None);
15696    });
15697    cx.set_state(indoc! {"ˇ
15698        wow
15699        wowen
15700        wowser
15701    "});
15702    cx.simulate_keystroke("w");
15703    cx.executor().run_until_parked();
15704    cx.update_editor(|editor, _, _| {
15705        if editor.context_menu.borrow_mut().is_some() {
15706            panic!("expected completion menu to be hidden, as disabled in settings");
15707        }
15708    });
15709}
15710
15711fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15712    let position = || lsp::Position {
15713        line: params.text_document_position.position.line,
15714        character: params.text_document_position.position.character,
15715    };
15716    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15717        range: lsp::Range {
15718            start: position(),
15719            end: position(),
15720        },
15721        new_text: text.to_string(),
15722    }))
15723}
15724
15725#[gpui::test]
15726async fn test_multiline_completion(cx: &mut TestAppContext) {
15727    init_test(cx, |_| {});
15728
15729    let fs = FakeFs::new(cx.executor());
15730    fs.insert_tree(
15731        path!("/a"),
15732        json!({
15733            "main.ts": "a",
15734        }),
15735    )
15736    .await;
15737
15738    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15739    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15740    let typescript_language = Arc::new(Language::new(
15741        LanguageConfig {
15742            name: "TypeScript".into(),
15743            matcher: LanguageMatcher {
15744                path_suffixes: vec!["ts".to_string()],
15745                ..LanguageMatcher::default()
15746            },
15747            line_comments: vec!["// ".into()],
15748            ..LanguageConfig::default()
15749        },
15750        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15751    ));
15752    language_registry.add(typescript_language.clone());
15753    let mut fake_servers = language_registry.register_fake_lsp(
15754        "TypeScript",
15755        FakeLspAdapter {
15756            capabilities: lsp::ServerCapabilities {
15757                completion_provider: Some(lsp::CompletionOptions {
15758                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15759                    ..lsp::CompletionOptions::default()
15760                }),
15761                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15762                ..lsp::ServerCapabilities::default()
15763            },
15764            // Emulate vtsls label generation
15765            label_for_completion: Some(Box::new(|item, _| {
15766                let text = if let Some(description) = item
15767                    .label_details
15768                    .as_ref()
15769                    .and_then(|label_details| label_details.description.as_ref())
15770                {
15771                    format!("{} {}", item.label, description)
15772                } else if let Some(detail) = &item.detail {
15773                    format!("{} {}", item.label, detail)
15774                } else {
15775                    item.label.clone()
15776                };
15777                Some(language::CodeLabel::plain(text, None))
15778            })),
15779            ..FakeLspAdapter::default()
15780        },
15781    );
15782    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15783    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15784    let worktree_id = workspace
15785        .update(cx, |workspace, _window, cx| {
15786            workspace.project().update(cx, |project, cx| {
15787                project.worktrees(cx).next().unwrap().read(cx).id()
15788            })
15789        })
15790        .unwrap();
15791    let _buffer = project
15792        .update(cx, |project, cx| {
15793            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15794        })
15795        .await
15796        .unwrap();
15797    let editor = workspace
15798        .update(cx, |workspace, window, cx| {
15799            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15800        })
15801        .unwrap()
15802        .await
15803        .unwrap()
15804        .downcast::<Editor>()
15805        .unwrap();
15806    let fake_server = fake_servers.next().await.unwrap();
15807
15808    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15809    let multiline_label_2 = "a\nb\nc\n";
15810    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15811    let multiline_description = "d\ne\nf\n";
15812    let multiline_detail_2 = "g\nh\ni\n";
15813
15814    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15815        move |params, _| async move {
15816            Ok(Some(lsp::CompletionResponse::Array(vec![
15817                lsp::CompletionItem {
15818                    label: multiline_label.to_string(),
15819                    text_edit: gen_text_edit(&params, "new_text_1"),
15820                    ..lsp::CompletionItem::default()
15821                },
15822                lsp::CompletionItem {
15823                    label: "single line label 1".to_string(),
15824                    detail: Some(multiline_detail.to_string()),
15825                    text_edit: gen_text_edit(&params, "new_text_2"),
15826                    ..lsp::CompletionItem::default()
15827                },
15828                lsp::CompletionItem {
15829                    label: "single line label 2".to_string(),
15830                    label_details: Some(lsp::CompletionItemLabelDetails {
15831                        description: Some(multiline_description.to_string()),
15832                        detail: None,
15833                    }),
15834                    text_edit: gen_text_edit(&params, "new_text_2"),
15835                    ..lsp::CompletionItem::default()
15836                },
15837                lsp::CompletionItem {
15838                    label: multiline_label_2.to_string(),
15839                    detail: Some(multiline_detail_2.to_string()),
15840                    text_edit: gen_text_edit(&params, "new_text_3"),
15841                    ..lsp::CompletionItem::default()
15842                },
15843                lsp::CompletionItem {
15844                    label: "Label with many     spaces and \t but without newlines".to_string(),
15845                    detail: Some(
15846                        "Details with many     spaces and \t but without newlines".to_string(),
15847                    ),
15848                    text_edit: gen_text_edit(&params, "new_text_4"),
15849                    ..lsp::CompletionItem::default()
15850                },
15851            ])))
15852        },
15853    );
15854
15855    editor.update_in(cx, |editor, window, cx| {
15856        cx.focus_self(window);
15857        editor.move_to_end(&MoveToEnd, window, cx);
15858        editor.handle_input(".", window, cx);
15859    });
15860    cx.run_until_parked();
15861    completion_handle.next().await.unwrap();
15862
15863    editor.update(cx, |editor, _| {
15864        assert!(editor.context_menu_visible());
15865        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15866        {
15867            let completion_labels = menu
15868                .completions
15869                .borrow()
15870                .iter()
15871                .map(|c| c.label.text.clone())
15872                .collect::<Vec<_>>();
15873            assert_eq!(
15874                completion_labels,
15875                &[
15876                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15877                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15878                    "single line label 2 d e f ",
15879                    "a b c g h i ",
15880                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15881                ],
15882                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15883            );
15884
15885            for completion in menu
15886                .completions
15887                .borrow()
15888                .iter() {
15889                    assert_eq!(
15890                        completion.label.filter_range,
15891                        0..completion.label.text.len(),
15892                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15893                    );
15894                }
15895        } else {
15896            panic!("expected completion menu to be open");
15897        }
15898    });
15899}
15900
15901#[gpui::test]
15902async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15903    init_test(cx, |_| {});
15904    let mut cx = EditorLspTestContext::new_rust(
15905        lsp::ServerCapabilities {
15906            completion_provider: Some(lsp::CompletionOptions {
15907                trigger_characters: Some(vec![".".to_string()]),
15908                ..Default::default()
15909            }),
15910            ..Default::default()
15911        },
15912        cx,
15913    )
15914    .await;
15915    cx.lsp
15916        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15917            Ok(Some(lsp::CompletionResponse::Array(vec![
15918                lsp::CompletionItem {
15919                    label: "first".into(),
15920                    ..Default::default()
15921                },
15922                lsp::CompletionItem {
15923                    label: "last".into(),
15924                    ..Default::default()
15925                },
15926            ])))
15927        });
15928    cx.set_state("variableˇ");
15929    cx.simulate_keystroke(".");
15930    cx.executor().run_until_parked();
15931
15932    cx.update_editor(|editor, _, _| {
15933        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15934        {
15935            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15936        } else {
15937            panic!("expected completion menu to be open");
15938        }
15939    });
15940
15941    cx.update_editor(|editor, window, cx| {
15942        editor.move_page_down(&MovePageDown::default(), window, cx);
15943        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15944        {
15945            assert!(
15946                menu.selected_item == 1,
15947                "expected PageDown to select the last item from the context menu"
15948            );
15949        } else {
15950            panic!("expected completion menu to stay open after PageDown");
15951        }
15952    });
15953
15954    cx.update_editor(|editor, window, cx| {
15955        editor.move_page_up(&MovePageUp::default(), window, cx);
15956        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15957        {
15958            assert!(
15959                menu.selected_item == 0,
15960                "expected PageUp to select the first item from the context menu"
15961            );
15962        } else {
15963            panic!("expected completion menu to stay open after PageUp");
15964        }
15965    });
15966}
15967
15968#[gpui::test]
15969async fn test_as_is_completions(cx: &mut TestAppContext) {
15970    init_test(cx, |_| {});
15971    let mut cx = EditorLspTestContext::new_rust(
15972        lsp::ServerCapabilities {
15973            completion_provider: Some(lsp::CompletionOptions {
15974                ..Default::default()
15975            }),
15976            ..Default::default()
15977        },
15978        cx,
15979    )
15980    .await;
15981    cx.lsp
15982        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15983            Ok(Some(lsp::CompletionResponse::Array(vec![
15984                lsp::CompletionItem {
15985                    label: "unsafe".into(),
15986                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15987                        range: lsp::Range {
15988                            start: lsp::Position {
15989                                line: 1,
15990                                character: 2,
15991                            },
15992                            end: lsp::Position {
15993                                line: 1,
15994                                character: 3,
15995                            },
15996                        },
15997                        new_text: "unsafe".to_string(),
15998                    })),
15999                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
16000                    ..Default::default()
16001                },
16002            ])))
16003        });
16004    cx.set_state("fn a() {}\n");
16005    cx.executor().run_until_parked();
16006    cx.update_editor(|editor, window, cx| {
16007        editor.trigger_completion_on_input("n", true, window, cx)
16008    });
16009    cx.executor().run_until_parked();
16010
16011    cx.update_editor(|editor, window, cx| {
16012        editor.confirm_completion(&Default::default(), window, cx)
16013    });
16014    cx.executor().run_until_parked();
16015    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
16016}
16017
16018#[gpui::test]
16019async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
16020    init_test(cx, |_| {});
16021    let language =
16022        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
16023    let mut cx = EditorLspTestContext::new(
16024        language,
16025        lsp::ServerCapabilities {
16026            completion_provider: Some(lsp::CompletionOptions {
16027                ..lsp::CompletionOptions::default()
16028            }),
16029            ..lsp::ServerCapabilities::default()
16030        },
16031        cx,
16032    )
16033    .await;
16034
16035    cx.set_state(
16036        "#ifndef BAR_H
16037#define BAR_H
16038
16039#include <stdbool.h>
16040
16041int fn_branch(bool do_branch1, bool do_branch2);
16042
16043#endif // BAR_H
16044ˇ",
16045    );
16046    cx.executor().run_until_parked();
16047    cx.update_editor(|editor, window, cx| {
16048        editor.handle_input("#", window, cx);
16049    });
16050    cx.executor().run_until_parked();
16051    cx.update_editor(|editor, window, cx| {
16052        editor.handle_input("i", window, cx);
16053    });
16054    cx.executor().run_until_parked();
16055    cx.update_editor(|editor, window, cx| {
16056        editor.handle_input("n", window, cx);
16057    });
16058    cx.executor().run_until_parked();
16059    cx.assert_editor_state(
16060        "#ifndef BAR_H
16061#define BAR_H
16062
16063#include <stdbool.h>
16064
16065int fn_branch(bool do_branch1, bool do_branch2);
16066
16067#endif // BAR_H
16068#inˇ",
16069    );
16070
16071    cx.lsp
16072        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16073            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16074                is_incomplete: false,
16075                item_defaults: None,
16076                items: vec![lsp::CompletionItem {
16077                    kind: Some(lsp::CompletionItemKind::SNIPPET),
16078                    label_details: Some(lsp::CompletionItemLabelDetails {
16079                        detail: Some("header".to_string()),
16080                        description: None,
16081                    }),
16082                    label: " include".to_string(),
16083                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16084                        range: lsp::Range {
16085                            start: lsp::Position {
16086                                line: 8,
16087                                character: 1,
16088                            },
16089                            end: lsp::Position {
16090                                line: 8,
16091                                character: 1,
16092                            },
16093                        },
16094                        new_text: "include \"$0\"".to_string(),
16095                    })),
16096                    sort_text: Some("40b67681include".to_string()),
16097                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16098                    filter_text: Some("include".to_string()),
16099                    insert_text: Some("include \"$0\"".to_string()),
16100                    ..lsp::CompletionItem::default()
16101                }],
16102            })))
16103        });
16104    cx.update_editor(|editor, window, cx| {
16105        editor.show_completions(&ShowCompletions, window, cx);
16106    });
16107    cx.executor().run_until_parked();
16108    cx.update_editor(|editor, window, cx| {
16109        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16110    });
16111    cx.executor().run_until_parked();
16112    cx.assert_editor_state(
16113        "#ifndef BAR_H
16114#define BAR_H
16115
16116#include <stdbool.h>
16117
16118int fn_branch(bool do_branch1, bool do_branch2);
16119
16120#endif // BAR_H
16121#include \"ˇ\"",
16122    );
16123
16124    cx.lsp
16125        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16126            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16127                is_incomplete: true,
16128                item_defaults: None,
16129                items: vec![lsp::CompletionItem {
16130                    kind: Some(lsp::CompletionItemKind::FILE),
16131                    label: "AGL/".to_string(),
16132                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16133                        range: lsp::Range {
16134                            start: lsp::Position {
16135                                line: 8,
16136                                character: 10,
16137                            },
16138                            end: lsp::Position {
16139                                line: 8,
16140                                character: 11,
16141                            },
16142                        },
16143                        new_text: "AGL/".to_string(),
16144                    })),
16145                    sort_text: Some("40b67681AGL/".to_string()),
16146                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16147                    filter_text: Some("AGL/".to_string()),
16148                    insert_text: Some("AGL/".to_string()),
16149                    ..lsp::CompletionItem::default()
16150                }],
16151            })))
16152        });
16153    cx.update_editor(|editor, window, cx| {
16154        editor.show_completions(&ShowCompletions, window, cx);
16155    });
16156    cx.executor().run_until_parked();
16157    cx.update_editor(|editor, window, cx| {
16158        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16159    });
16160    cx.executor().run_until_parked();
16161    cx.assert_editor_state(
16162        r##"#ifndef BAR_H
16163#define BAR_H
16164
16165#include <stdbool.h>
16166
16167int fn_branch(bool do_branch1, bool do_branch2);
16168
16169#endif // BAR_H
16170#include "AGL/ˇ"##,
16171    );
16172
16173    cx.update_editor(|editor, window, cx| {
16174        editor.handle_input("\"", window, cx);
16175    });
16176    cx.executor().run_until_parked();
16177    cx.assert_editor_state(
16178        r##"#ifndef BAR_H
16179#define BAR_H
16180
16181#include <stdbool.h>
16182
16183int fn_branch(bool do_branch1, bool do_branch2);
16184
16185#endif // BAR_H
16186#include "AGL/"ˇ"##,
16187    );
16188}
16189
16190#[gpui::test]
16191async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16192    init_test(cx, |_| {});
16193
16194    let mut cx = EditorLspTestContext::new_rust(
16195        lsp::ServerCapabilities {
16196            completion_provider: Some(lsp::CompletionOptions {
16197                trigger_characters: Some(vec![".".to_string()]),
16198                resolve_provider: Some(true),
16199                ..Default::default()
16200            }),
16201            ..Default::default()
16202        },
16203        cx,
16204    )
16205    .await;
16206
16207    cx.set_state("fn main() { let a = 2ˇ; }");
16208    cx.simulate_keystroke(".");
16209    let completion_item = lsp::CompletionItem {
16210        label: "Some".into(),
16211        kind: Some(lsp::CompletionItemKind::SNIPPET),
16212        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16213        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16214            kind: lsp::MarkupKind::Markdown,
16215            value: "```rust\nSome(2)\n```".to_string(),
16216        })),
16217        deprecated: Some(false),
16218        sort_text: Some("Some".to_string()),
16219        filter_text: Some("Some".to_string()),
16220        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16221        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16222            range: lsp::Range {
16223                start: lsp::Position {
16224                    line: 0,
16225                    character: 22,
16226                },
16227                end: lsp::Position {
16228                    line: 0,
16229                    character: 22,
16230                },
16231            },
16232            new_text: "Some(2)".to_string(),
16233        })),
16234        additional_text_edits: Some(vec![lsp::TextEdit {
16235            range: lsp::Range {
16236                start: lsp::Position {
16237                    line: 0,
16238                    character: 20,
16239                },
16240                end: lsp::Position {
16241                    line: 0,
16242                    character: 22,
16243                },
16244            },
16245            new_text: "".to_string(),
16246        }]),
16247        ..Default::default()
16248    };
16249
16250    let closure_completion_item = completion_item.clone();
16251    let counter = Arc::new(AtomicUsize::new(0));
16252    let counter_clone = counter.clone();
16253    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16254        let task_completion_item = closure_completion_item.clone();
16255        counter_clone.fetch_add(1, atomic::Ordering::Release);
16256        async move {
16257            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16258                is_incomplete: true,
16259                item_defaults: None,
16260                items: vec![task_completion_item],
16261            })))
16262        }
16263    });
16264
16265    cx.condition(|editor, _| editor.context_menu_visible())
16266        .await;
16267    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16268    assert!(request.next().await.is_some());
16269    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16270
16271    cx.simulate_keystrokes("S o m");
16272    cx.condition(|editor, _| editor.context_menu_visible())
16273        .await;
16274    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16275    assert!(request.next().await.is_some());
16276    assert!(request.next().await.is_some());
16277    assert!(request.next().await.is_some());
16278    request.close();
16279    assert!(request.next().await.is_none());
16280    assert_eq!(
16281        counter.load(atomic::Ordering::Acquire),
16282        4,
16283        "With the completions menu open, only one LSP request should happen per input"
16284    );
16285}
16286
16287#[gpui::test]
16288async fn test_toggle_comment(cx: &mut TestAppContext) {
16289    init_test(cx, |_| {});
16290    let mut cx = EditorTestContext::new(cx).await;
16291    let language = Arc::new(Language::new(
16292        LanguageConfig {
16293            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16294            ..Default::default()
16295        },
16296        Some(tree_sitter_rust::LANGUAGE.into()),
16297    ));
16298    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16299
16300    // If multiple selections intersect a line, the line is only toggled once.
16301    cx.set_state(indoc! {"
16302        fn a() {
16303            «//b();
16304            ˇ»// «c();
16305            //ˇ»  d();
16306        }
16307    "});
16308
16309    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16310
16311    cx.assert_editor_state(indoc! {"
16312        fn a() {
16313            «b();
16314            ˇ»«c();
16315            ˇ» d();
16316        }
16317    "});
16318
16319    // The comment prefix is inserted at the same column for every line in a
16320    // selection.
16321    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16322
16323    cx.assert_editor_state(indoc! {"
16324        fn a() {
16325            // «b();
16326            ˇ»// «c();
16327            ˇ» // d();
16328        }
16329    "});
16330
16331    // If a selection ends at the beginning of a line, that line is not toggled.
16332    cx.set_selections_state(indoc! {"
16333        fn a() {
16334            // b();
16335            «// c();
16336        ˇ»     // d();
16337        }
16338    "});
16339
16340    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16341
16342    cx.assert_editor_state(indoc! {"
16343        fn a() {
16344            // b();
16345            «c();
16346        ˇ»     // d();
16347        }
16348    "});
16349
16350    // If a selection span a single line and is empty, the line is toggled.
16351    cx.set_state(indoc! {"
16352        fn a() {
16353            a();
16354            b();
16355        ˇ
16356        }
16357    "});
16358
16359    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16360
16361    cx.assert_editor_state(indoc! {"
16362        fn a() {
16363            a();
16364            b();
16365        //•ˇ
16366        }
16367    "});
16368
16369    // If a selection span multiple lines, empty lines are not toggled.
16370    cx.set_state(indoc! {"
16371        fn a() {
16372            «a();
16373
16374            c();ˇ»
16375        }
16376    "});
16377
16378    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16379
16380    cx.assert_editor_state(indoc! {"
16381        fn a() {
16382            // «a();
16383
16384            // c();ˇ»
16385        }
16386    "});
16387
16388    // If a selection includes multiple comment prefixes, all lines are uncommented.
16389    cx.set_state(indoc! {"
16390        fn a() {
16391            «// a();
16392            /// b();
16393            //! c();ˇ»
16394        }
16395    "});
16396
16397    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16398
16399    cx.assert_editor_state(indoc! {"
16400        fn a() {
16401            «a();
16402            b();
16403            c();ˇ»
16404        }
16405    "});
16406}
16407
16408#[gpui::test]
16409async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16410    init_test(cx, |_| {});
16411    let mut cx = EditorTestContext::new(cx).await;
16412    let language = Arc::new(Language::new(
16413        LanguageConfig {
16414            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16415            ..Default::default()
16416        },
16417        Some(tree_sitter_rust::LANGUAGE.into()),
16418    ));
16419    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16420
16421    let toggle_comments = &ToggleComments {
16422        advance_downwards: false,
16423        ignore_indent: true,
16424    };
16425
16426    // If multiple selections intersect a line, the line is only toggled once.
16427    cx.set_state(indoc! {"
16428        fn a() {
16429        //    «b();
16430        //    c();
16431        //    ˇ» d();
16432        }
16433    "});
16434
16435    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16436
16437    cx.assert_editor_state(indoc! {"
16438        fn a() {
16439            «b();
16440            c();
16441            ˇ» d();
16442        }
16443    "});
16444
16445    // The comment prefix is inserted at the beginning of each line
16446    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16447
16448    cx.assert_editor_state(indoc! {"
16449        fn a() {
16450        //    «b();
16451        //    c();
16452        //    ˇ» d();
16453        }
16454    "});
16455
16456    // If a selection ends at the beginning of a line, that line is not toggled.
16457    cx.set_selections_state(indoc! {"
16458        fn a() {
16459        //    b();
16460        //    «c();
16461        ˇ»//     d();
16462        }
16463    "});
16464
16465    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16466
16467    cx.assert_editor_state(indoc! {"
16468        fn a() {
16469        //    b();
16470            «c();
16471        ˇ»//     d();
16472        }
16473    "});
16474
16475    // If a selection span a single line and is empty, the line is toggled.
16476    cx.set_state(indoc! {"
16477        fn a() {
16478            a();
16479            b();
16480        ˇ
16481        }
16482    "});
16483
16484    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16485
16486    cx.assert_editor_state(indoc! {"
16487        fn a() {
16488            a();
16489            b();
16490        //ˇ
16491        }
16492    "});
16493
16494    // If a selection span multiple lines, empty lines are not toggled.
16495    cx.set_state(indoc! {"
16496        fn a() {
16497            «a();
16498
16499            c();ˇ»
16500        }
16501    "});
16502
16503    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16504
16505    cx.assert_editor_state(indoc! {"
16506        fn a() {
16507        //    «a();
16508
16509        //    c();ˇ»
16510        }
16511    "});
16512
16513    // If a selection includes multiple comment prefixes, all lines are uncommented.
16514    cx.set_state(indoc! {"
16515        fn a() {
16516        //    «a();
16517        ///    b();
16518        //!    c();ˇ»
16519        }
16520    "});
16521
16522    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16523
16524    cx.assert_editor_state(indoc! {"
16525        fn a() {
16526            «a();
16527            b();
16528            c();ˇ»
16529        }
16530    "});
16531}
16532
16533#[gpui::test]
16534async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16535    init_test(cx, |_| {});
16536
16537    let language = Arc::new(Language::new(
16538        LanguageConfig {
16539            line_comments: vec!["// ".into()],
16540            ..Default::default()
16541        },
16542        Some(tree_sitter_rust::LANGUAGE.into()),
16543    ));
16544
16545    let mut cx = EditorTestContext::new(cx).await;
16546
16547    cx.language_registry().add(language.clone());
16548    cx.update_buffer(|buffer, cx| {
16549        buffer.set_language(Some(language), cx);
16550    });
16551
16552    let toggle_comments = &ToggleComments {
16553        advance_downwards: true,
16554        ignore_indent: false,
16555    };
16556
16557    // Single cursor on one line -> advance
16558    // Cursor moves horizontally 3 characters as well on non-blank line
16559    cx.set_state(indoc!(
16560        "fn a() {
16561             ˇdog();
16562             cat();
16563        }"
16564    ));
16565    cx.update_editor(|editor, window, cx| {
16566        editor.toggle_comments(toggle_comments, window, cx);
16567    });
16568    cx.assert_editor_state(indoc!(
16569        "fn a() {
16570             // dog();
16571             catˇ();
16572        }"
16573    ));
16574
16575    // Single selection on one line -> don't advance
16576    cx.set_state(indoc!(
16577        "fn a() {
16578             «dog()ˇ»;
16579             cat();
16580        }"
16581    ));
16582    cx.update_editor(|editor, window, cx| {
16583        editor.toggle_comments(toggle_comments, window, cx);
16584    });
16585    cx.assert_editor_state(indoc!(
16586        "fn a() {
16587             // «dog()ˇ»;
16588             cat();
16589        }"
16590    ));
16591
16592    // Multiple cursors on one line -> advance
16593    cx.set_state(indoc!(
16594        "fn a() {
16595             ˇdˇog();
16596             cat();
16597        }"
16598    ));
16599    cx.update_editor(|editor, window, cx| {
16600        editor.toggle_comments(toggle_comments, window, cx);
16601    });
16602    cx.assert_editor_state(indoc!(
16603        "fn a() {
16604             // dog();
16605             catˇ(ˇ);
16606        }"
16607    ));
16608
16609    // Multiple cursors on one line, with selection -> don't advance
16610    cx.set_state(indoc!(
16611        "fn a() {
16612             ˇdˇog«()ˇ»;
16613             cat();
16614        }"
16615    ));
16616    cx.update_editor(|editor, window, cx| {
16617        editor.toggle_comments(toggle_comments, window, cx);
16618    });
16619    cx.assert_editor_state(indoc!(
16620        "fn a() {
16621             // ˇdˇog«()ˇ»;
16622             cat();
16623        }"
16624    ));
16625
16626    // Single cursor on one line -> advance
16627    // Cursor moves to column 0 on blank line
16628    cx.set_state(indoc!(
16629        "fn a() {
16630             ˇdog();
16631
16632             cat();
16633        }"
16634    ));
16635    cx.update_editor(|editor, window, cx| {
16636        editor.toggle_comments(toggle_comments, window, cx);
16637    });
16638    cx.assert_editor_state(indoc!(
16639        "fn a() {
16640             // dog();
16641        ˇ
16642             cat();
16643        }"
16644    ));
16645
16646    // Single cursor on one line -> advance
16647    // Cursor starts and ends at column 0
16648    cx.set_state(indoc!(
16649        "fn a() {
16650         ˇ    dog();
16651             cat();
16652        }"
16653    ));
16654    cx.update_editor(|editor, window, cx| {
16655        editor.toggle_comments(toggle_comments, window, cx);
16656    });
16657    cx.assert_editor_state(indoc!(
16658        "fn a() {
16659             // dog();
16660         ˇ    cat();
16661        }"
16662    ));
16663}
16664
16665#[gpui::test]
16666async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16667    init_test(cx, |_| {});
16668
16669    let mut cx = EditorTestContext::new(cx).await;
16670
16671    let html_language = Arc::new(
16672        Language::new(
16673            LanguageConfig {
16674                name: "HTML".into(),
16675                block_comment: Some(BlockCommentConfig {
16676                    start: "<!-- ".into(),
16677                    prefix: "".into(),
16678                    end: " -->".into(),
16679                    tab_size: 0,
16680                }),
16681                ..Default::default()
16682            },
16683            Some(tree_sitter_html::LANGUAGE.into()),
16684        )
16685        .with_injection_query(
16686            r#"
16687            (script_element
16688                (raw_text) @injection.content
16689                (#set! injection.language "javascript"))
16690            "#,
16691        )
16692        .unwrap(),
16693    );
16694
16695    let javascript_language = Arc::new(Language::new(
16696        LanguageConfig {
16697            name: "JavaScript".into(),
16698            line_comments: vec!["// ".into()],
16699            ..Default::default()
16700        },
16701        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16702    ));
16703
16704    cx.language_registry().add(html_language.clone());
16705    cx.language_registry().add(javascript_language);
16706    cx.update_buffer(|buffer, cx| {
16707        buffer.set_language(Some(html_language), cx);
16708    });
16709
16710    // Toggle comments for empty selections
16711    cx.set_state(
16712        &r#"
16713            <p>A</p>ˇ
16714            <p>B</p>ˇ
16715            <p>C</p>ˇ
16716        "#
16717        .unindent(),
16718    );
16719    cx.update_editor(|editor, window, cx| {
16720        editor.toggle_comments(&ToggleComments::default(), window, cx)
16721    });
16722    cx.assert_editor_state(
16723        &r#"
16724            <!-- <p>A</p>ˇ -->
16725            <!-- <p>B</p>ˇ -->
16726            <!-- <p>C</p>ˇ -->
16727        "#
16728        .unindent(),
16729    );
16730    cx.update_editor(|editor, window, cx| {
16731        editor.toggle_comments(&ToggleComments::default(), window, cx)
16732    });
16733    cx.assert_editor_state(
16734        &r#"
16735            <p>A</p>ˇ
16736            <p>B</p>ˇ
16737            <p>C</p>ˇ
16738        "#
16739        .unindent(),
16740    );
16741
16742    // Toggle comments for mixture of empty and non-empty selections, where
16743    // multiple selections occupy a given line.
16744    cx.set_state(
16745        &r#"
16746            <p>A«</p>
16747            <p>ˇ»B</p>ˇ
16748            <p>C«</p>
16749            <p>ˇ»D</p>ˇ
16750        "#
16751        .unindent(),
16752    );
16753
16754    cx.update_editor(|editor, window, cx| {
16755        editor.toggle_comments(&ToggleComments::default(), window, cx)
16756    });
16757    cx.assert_editor_state(
16758        &r#"
16759            <!-- <p>A«</p>
16760            <p>ˇ»B</p>ˇ -->
16761            <!-- <p>C«</p>
16762            <p>ˇ»D</p>ˇ -->
16763        "#
16764        .unindent(),
16765    );
16766    cx.update_editor(|editor, window, cx| {
16767        editor.toggle_comments(&ToggleComments::default(), window, cx)
16768    });
16769    cx.assert_editor_state(
16770        &r#"
16771            <p>A«</p>
16772            <p>ˇ»B</p>ˇ
16773            <p>C«</p>
16774            <p>ˇ»D</p>ˇ
16775        "#
16776        .unindent(),
16777    );
16778
16779    // Toggle comments when different languages are active for different
16780    // selections.
16781    cx.set_state(
16782        &r#"
16783            ˇ<script>
16784                ˇvar x = new Y();
16785            ˇ</script>
16786        "#
16787        .unindent(),
16788    );
16789    cx.executor().run_until_parked();
16790    cx.update_editor(|editor, window, cx| {
16791        editor.toggle_comments(&ToggleComments::default(), window, cx)
16792    });
16793    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16794    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16795    cx.assert_editor_state(
16796        &r#"
16797            <!-- ˇ<script> -->
16798                // ˇvar x = new Y();
16799            <!-- ˇ</script> -->
16800        "#
16801        .unindent(),
16802    );
16803}
16804
16805#[gpui::test]
16806fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16807    init_test(cx, |_| {});
16808
16809    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16810    let multibuffer = cx.new(|cx| {
16811        let mut multibuffer = MultiBuffer::new(ReadWrite);
16812        multibuffer.push_excerpts(
16813            buffer.clone(),
16814            [
16815                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16816                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16817            ],
16818            cx,
16819        );
16820        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16821        multibuffer
16822    });
16823
16824    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16825    editor.update_in(cx, |editor, window, cx| {
16826        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16827        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16828            s.select_ranges([
16829                Point::new(0, 0)..Point::new(0, 0),
16830                Point::new(1, 0)..Point::new(1, 0),
16831            ])
16832        });
16833
16834        editor.handle_input("X", window, cx);
16835        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16836        assert_eq!(
16837            editor.selections.ranges(&editor.display_snapshot(cx)),
16838            [
16839                Point::new(0, 1)..Point::new(0, 1),
16840                Point::new(1, 1)..Point::new(1, 1),
16841            ]
16842        );
16843
16844        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16845        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16846            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16847        });
16848        editor.backspace(&Default::default(), window, cx);
16849        assert_eq!(editor.text(cx), "Xa\nbbb");
16850        assert_eq!(
16851            editor.selections.ranges(&editor.display_snapshot(cx)),
16852            [Point::new(1, 0)..Point::new(1, 0)]
16853        );
16854
16855        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16856            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16857        });
16858        editor.backspace(&Default::default(), window, cx);
16859        assert_eq!(editor.text(cx), "X\nbb");
16860        assert_eq!(
16861            editor.selections.ranges(&editor.display_snapshot(cx)),
16862            [Point::new(0, 1)..Point::new(0, 1)]
16863        );
16864    });
16865}
16866
16867#[gpui::test]
16868fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16869    init_test(cx, |_| {});
16870
16871    let markers = vec![('[', ']').into(), ('(', ')').into()];
16872    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16873        indoc! {"
16874            [aaaa
16875            (bbbb]
16876            cccc)",
16877        },
16878        markers.clone(),
16879    );
16880    let excerpt_ranges = markers.into_iter().map(|marker| {
16881        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16882        ExcerptRange::new(context)
16883    });
16884    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16885    let multibuffer = cx.new(|cx| {
16886        let mut multibuffer = MultiBuffer::new(ReadWrite);
16887        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16888        multibuffer
16889    });
16890
16891    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16892    editor.update_in(cx, |editor, window, cx| {
16893        let (expected_text, selection_ranges) = marked_text_ranges(
16894            indoc! {"
16895                aaaa
16896                bˇbbb
16897                bˇbbˇb
16898                cccc"
16899            },
16900            true,
16901        );
16902        assert_eq!(editor.text(cx), expected_text);
16903        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16904            s.select_ranges(
16905                selection_ranges
16906                    .iter()
16907                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16908            )
16909        });
16910
16911        editor.handle_input("X", window, cx);
16912
16913        let (expected_text, expected_selections) = marked_text_ranges(
16914            indoc! {"
16915                aaaa
16916                bXˇbbXb
16917                bXˇbbXˇb
16918                cccc"
16919            },
16920            false,
16921        );
16922        assert_eq!(editor.text(cx), expected_text);
16923        assert_eq!(
16924            editor.selections.ranges(&editor.display_snapshot(cx)),
16925            expected_selections
16926                .iter()
16927                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16928                .collect::<Vec<_>>()
16929        );
16930
16931        editor.newline(&Newline, window, cx);
16932        let (expected_text, expected_selections) = marked_text_ranges(
16933            indoc! {"
16934                aaaa
16935                bX
16936                ˇbbX
16937                b
16938                bX
16939                ˇbbX
16940                ˇb
16941                cccc"
16942            },
16943            false,
16944        );
16945        assert_eq!(editor.text(cx), expected_text);
16946        assert_eq!(
16947            editor.selections.ranges(&editor.display_snapshot(cx)),
16948            expected_selections
16949                .iter()
16950                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16951                .collect::<Vec<_>>()
16952        );
16953    });
16954}
16955
16956#[gpui::test]
16957fn test_refresh_selections(cx: &mut TestAppContext) {
16958    init_test(cx, |_| {});
16959
16960    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16961    let mut excerpt1_id = None;
16962    let multibuffer = cx.new(|cx| {
16963        let mut multibuffer = MultiBuffer::new(ReadWrite);
16964        excerpt1_id = multibuffer
16965            .push_excerpts(
16966                buffer.clone(),
16967                [
16968                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16969                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16970                ],
16971                cx,
16972            )
16973            .into_iter()
16974            .next();
16975        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16976        multibuffer
16977    });
16978
16979    let editor = cx.add_window(|window, cx| {
16980        let mut editor = build_editor(multibuffer.clone(), window, cx);
16981        let snapshot = editor.snapshot(window, cx);
16982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16983            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16984        });
16985        editor.begin_selection(
16986            Point::new(2, 1).to_display_point(&snapshot),
16987            true,
16988            1,
16989            window,
16990            cx,
16991        );
16992        assert_eq!(
16993            editor.selections.ranges(&editor.display_snapshot(cx)),
16994            [
16995                Point::new(1, 3)..Point::new(1, 3),
16996                Point::new(2, 1)..Point::new(2, 1),
16997            ]
16998        );
16999        editor
17000    });
17001
17002    // Refreshing selections is a no-op when excerpts haven't changed.
17003    _ = editor.update(cx, |editor, window, cx| {
17004        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17005        assert_eq!(
17006            editor.selections.ranges(&editor.display_snapshot(cx)),
17007            [
17008                Point::new(1, 3)..Point::new(1, 3),
17009                Point::new(2, 1)..Point::new(2, 1),
17010            ]
17011        );
17012    });
17013
17014    multibuffer.update(cx, |multibuffer, cx| {
17015        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17016    });
17017    _ = editor.update(cx, |editor, window, cx| {
17018        // Removing an excerpt causes the first selection to become degenerate.
17019        assert_eq!(
17020            editor.selections.ranges(&editor.display_snapshot(cx)),
17021            [
17022                Point::new(0, 0)..Point::new(0, 0),
17023                Point::new(0, 1)..Point::new(0, 1)
17024            ]
17025        );
17026
17027        // Refreshing selections will relocate the first selection to the original buffer
17028        // location.
17029        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17030        assert_eq!(
17031            editor.selections.ranges(&editor.display_snapshot(cx)),
17032            [
17033                Point::new(0, 1)..Point::new(0, 1),
17034                Point::new(0, 3)..Point::new(0, 3)
17035            ]
17036        );
17037        assert!(editor.selections.pending_anchor().is_some());
17038    });
17039}
17040
17041#[gpui::test]
17042fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
17043    init_test(cx, |_| {});
17044
17045    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
17046    let mut excerpt1_id = None;
17047    let multibuffer = cx.new(|cx| {
17048        let mut multibuffer = MultiBuffer::new(ReadWrite);
17049        excerpt1_id = multibuffer
17050            .push_excerpts(
17051                buffer.clone(),
17052                [
17053                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
17054                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
17055                ],
17056                cx,
17057            )
17058            .into_iter()
17059            .next();
17060        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
17061        multibuffer
17062    });
17063
17064    let editor = cx.add_window(|window, cx| {
17065        let mut editor = build_editor(multibuffer.clone(), window, cx);
17066        let snapshot = editor.snapshot(window, cx);
17067        editor.begin_selection(
17068            Point::new(1, 3).to_display_point(&snapshot),
17069            false,
17070            1,
17071            window,
17072            cx,
17073        );
17074        assert_eq!(
17075            editor.selections.ranges(&editor.display_snapshot(cx)),
17076            [Point::new(1, 3)..Point::new(1, 3)]
17077        );
17078        editor
17079    });
17080
17081    multibuffer.update(cx, |multibuffer, cx| {
17082        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
17083    });
17084    _ = editor.update(cx, |editor, window, cx| {
17085        assert_eq!(
17086            editor.selections.ranges(&editor.display_snapshot(cx)),
17087            [Point::new(0, 0)..Point::new(0, 0)]
17088        );
17089
17090        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
17091        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
17092        assert_eq!(
17093            editor.selections.ranges(&editor.display_snapshot(cx)),
17094            [Point::new(0, 3)..Point::new(0, 3)]
17095        );
17096        assert!(editor.selections.pending_anchor().is_some());
17097    });
17098}
17099
17100#[gpui::test]
17101async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
17102    init_test(cx, |_| {});
17103
17104    let language = Arc::new(
17105        Language::new(
17106            LanguageConfig {
17107                brackets: BracketPairConfig {
17108                    pairs: vec![
17109                        BracketPair {
17110                            start: "{".to_string(),
17111                            end: "}".to_string(),
17112                            close: true,
17113                            surround: true,
17114                            newline: true,
17115                        },
17116                        BracketPair {
17117                            start: "/* ".to_string(),
17118                            end: " */".to_string(),
17119                            close: true,
17120                            surround: true,
17121                            newline: true,
17122                        },
17123                    ],
17124                    ..Default::default()
17125                },
17126                ..Default::default()
17127            },
17128            Some(tree_sitter_rust::LANGUAGE.into()),
17129        )
17130        .with_indents_query("")
17131        .unwrap(),
17132    );
17133
17134    let text = concat!(
17135        "{   }\n",     //
17136        "  x\n",       //
17137        "  /*   */\n", //
17138        "x\n",         //
17139        "{{} }\n",     //
17140    );
17141
17142    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17143    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17144    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17145    editor
17146        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17147        .await;
17148
17149    editor.update_in(cx, |editor, window, cx| {
17150        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17151            s.select_display_ranges([
17152                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17153                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17154                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17155            ])
17156        });
17157        editor.newline(&Newline, window, cx);
17158
17159        assert_eq!(
17160            editor.buffer().read(cx).read(cx).text(),
17161            concat!(
17162                "{ \n",    // Suppress rustfmt
17163                "\n",      //
17164                "}\n",     //
17165                "  x\n",   //
17166                "  /* \n", //
17167                "  \n",    //
17168                "  */\n",  //
17169                "x\n",     //
17170                "{{} \n",  //
17171                "}\n",     //
17172            )
17173        );
17174    });
17175}
17176
17177#[gpui::test]
17178fn test_highlighted_ranges(cx: &mut TestAppContext) {
17179    init_test(cx, |_| {});
17180
17181    let editor = cx.add_window(|window, cx| {
17182        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17183        build_editor(buffer, window, cx)
17184    });
17185
17186    _ = editor.update(cx, |editor, window, cx| {
17187        struct Type1;
17188        struct Type2;
17189
17190        let buffer = editor.buffer.read(cx).snapshot(cx);
17191
17192        let anchor_range =
17193            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17194
17195        editor.highlight_background::<Type1>(
17196            &[
17197                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17198                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17199                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17200                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17201            ],
17202            |_, _| Hsla::red(),
17203            cx,
17204        );
17205        editor.highlight_background::<Type2>(
17206            &[
17207                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17208                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17209                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17210                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17211            ],
17212            |_, _| Hsla::green(),
17213            cx,
17214        );
17215
17216        let snapshot = editor.snapshot(window, cx);
17217        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17218            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17219            &snapshot,
17220            cx.theme(),
17221        );
17222        assert_eq!(
17223            highlighted_ranges,
17224            &[
17225                (
17226                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17227                    Hsla::green(),
17228                ),
17229                (
17230                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17231                    Hsla::red(),
17232                ),
17233                (
17234                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17235                    Hsla::green(),
17236                ),
17237                (
17238                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17239                    Hsla::red(),
17240                ),
17241            ]
17242        );
17243        assert_eq!(
17244            editor.sorted_background_highlights_in_range(
17245                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17246                &snapshot,
17247                cx.theme(),
17248            ),
17249            &[(
17250                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17251                Hsla::red(),
17252            )]
17253        );
17254    });
17255}
17256
17257#[gpui::test]
17258async fn test_following(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
17264    let buffer = project.update(cx, |project, cx| {
17265        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17266        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17267    });
17268    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17269    let follower = cx.update(|cx| {
17270        cx.open_window(
17271            WindowOptions {
17272                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17273                    gpui::Point::new(px(0.), px(0.)),
17274                    gpui::Point::new(px(10.), px(80.)),
17275                ))),
17276                ..Default::default()
17277            },
17278            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17279        )
17280        .unwrap()
17281    });
17282
17283    let is_still_following = Rc::new(RefCell::new(true));
17284    let follower_edit_event_count = Rc::new(RefCell::new(0));
17285    let pending_update = Rc::new(RefCell::new(None));
17286    let leader_entity = leader.root(cx).unwrap();
17287    let follower_entity = follower.root(cx).unwrap();
17288    _ = follower.update(cx, {
17289        let update = pending_update.clone();
17290        let is_still_following = is_still_following.clone();
17291        let follower_edit_event_count = follower_edit_event_count.clone();
17292        |_, window, cx| {
17293            cx.subscribe_in(
17294                &leader_entity,
17295                window,
17296                move |_, leader, event, window, cx| {
17297                    leader.read(cx).add_event_to_update_proto(
17298                        event,
17299                        &mut update.borrow_mut(),
17300                        window,
17301                        cx,
17302                    );
17303                },
17304            )
17305            .detach();
17306
17307            cx.subscribe_in(
17308                &follower_entity,
17309                window,
17310                move |_, _, event: &EditorEvent, _window, _cx| {
17311                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17312                        *is_still_following.borrow_mut() = false;
17313                    }
17314
17315                    if let EditorEvent::BufferEdited = event {
17316                        *follower_edit_event_count.borrow_mut() += 1;
17317                    }
17318                },
17319            )
17320            .detach();
17321        }
17322    });
17323
17324    // Update the selections only
17325    _ = leader.update(cx, |leader, window, cx| {
17326        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17327            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17328        });
17329    });
17330    follower
17331        .update(cx, |follower, window, cx| {
17332            follower.apply_update_proto(
17333                &project,
17334                pending_update.borrow_mut().take().unwrap(),
17335                window,
17336                cx,
17337            )
17338        })
17339        .unwrap()
17340        .await
17341        .unwrap();
17342    _ = follower.update(cx, |follower, _, cx| {
17343        assert_eq!(
17344            follower.selections.ranges(&follower.display_snapshot(cx)),
17345            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17346        );
17347    });
17348    assert!(*is_still_following.borrow());
17349    assert_eq!(*follower_edit_event_count.borrow(), 0);
17350
17351    // Update the scroll position only
17352    _ = leader.update(cx, |leader, window, cx| {
17353        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17354    });
17355    follower
17356        .update(cx, |follower, window, cx| {
17357            follower.apply_update_proto(
17358                &project,
17359                pending_update.borrow_mut().take().unwrap(),
17360                window,
17361                cx,
17362            )
17363        })
17364        .unwrap()
17365        .await
17366        .unwrap();
17367    assert_eq!(
17368        follower
17369            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17370            .unwrap(),
17371        gpui::Point::new(1.5, 3.5)
17372    );
17373    assert!(*is_still_following.borrow());
17374    assert_eq!(*follower_edit_event_count.borrow(), 0);
17375
17376    // Update the selections and scroll position. The follower's scroll position is updated
17377    // via autoscroll, not via the leader's exact scroll position.
17378    _ = leader.update(cx, |leader, window, cx| {
17379        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17380            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17381        });
17382        leader.request_autoscroll(Autoscroll::newest(), cx);
17383        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17384    });
17385    follower
17386        .update(cx, |follower, window, cx| {
17387            follower.apply_update_proto(
17388                &project,
17389                pending_update.borrow_mut().take().unwrap(),
17390                window,
17391                cx,
17392            )
17393        })
17394        .unwrap()
17395        .await
17396        .unwrap();
17397    _ = follower.update(cx, |follower, _, cx| {
17398        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17399        assert_eq!(
17400            follower.selections.ranges(&follower.display_snapshot(cx)),
17401            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17402        );
17403    });
17404    assert!(*is_still_following.borrow());
17405
17406    // Creating a pending selection that precedes another selection
17407    _ = leader.update(cx, |leader, window, cx| {
17408        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17409            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17410        });
17411        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17412    });
17413    follower
17414        .update(cx, |follower, window, cx| {
17415            follower.apply_update_proto(
17416                &project,
17417                pending_update.borrow_mut().take().unwrap(),
17418                window,
17419                cx,
17420            )
17421        })
17422        .unwrap()
17423        .await
17424        .unwrap();
17425    _ = follower.update(cx, |follower, _, cx| {
17426        assert_eq!(
17427            follower.selections.ranges(&follower.display_snapshot(cx)),
17428            vec![
17429                MultiBufferOffset(0)..MultiBufferOffset(0),
17430                MultiBufferOffset(1)..MultiBufferOffset(1)
17431            ]
17432        );
17433    });
17434    assert!(*is_still_following.borrow());
17435
17436    // Extend the pending selection so that it surrounds another selection
17437    _ = leader.update(cx, |leader, window, cx| {
17438        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17439    });
17440    follower
17441        .update(cx, |follower, window, cx| {
17442            follower.apply_update_proto(
17443                &project,
17444                pending_update.borrow_mut().take().unwrap(),
17445                window,
17446                cx,
17447            )
17448        })
17449        .unwrap()
17450        .await
17451        .unwrap();
17452    _ = follower.update(cx, |follower, _, cx| {
17453        assert_eq!(
17454            follower.selections.ranges(&follower.display_snapshot(cx)),
17455            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17456        );
17457    });
17458
17459    // Scrolling locally breaks the follow
17460    _ = follower.update(cx, |follower, window, cx| {
17461        let top_anchor = follower
17462            .buffer()
17463            .read(cx)
17464            .read(cx)
17465            .anchor_after(MultiBufferOffset(0));
17466        follower.set_scroll_anchor(
17467            ScrollAnchor {
17468                anchor: top_anchor,
17469                offset: gpui::Point::new(0.0, 0.5),
17470            },
17471            window,
17472            cx,
17473        );
17474    });
17475    assert!(!(*is_still_following.borrow()));
17476}
17477
17478#[gpui::test]
17479async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17480    init_test(cx, |_| {});
17481
17482    let fs = FakeFs::new(cx.executor());
17483    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17484    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17485    let pane = workspace
17486        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17487        .unwrap();
17488
17489    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17490
17491    let leader = pane.update_in(cx, |_, window, cx| {
17492        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17493        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17494    });
17495
17496    // Start following the editor when it has no excerpts.
17497    let mut state_message =
17498        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17499    let workspace_entity = workspace.root(cx).unwrap();
17500    let follower_1 = cx
17501        .update_window(*workspace.deref(), |_, window, cx| {
17502            Editor::from_state_proto(
17503                workspace_entity,
17504                ViewId {
17505                    creator: CollaboratorId::PeerId(PeerId::default()),
17506                    id: 0,
17507                },
17508                &mut state_message,
17509                window,
17510                cx,
17511            )
17512        })
17513        .unwrap()
17514        .unwrap()
17515        .await
17516        .unwrap();
17517
17518    let update_message = Rc::new(RefCell::new(None));
17519    follower_1.update_in(cx, {
17520        let update = update_message.clone();
17521        |_, window, cx| {
17522            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17523                leader.read(cx).add_event_to_update_proto(
17524                    event,
17525                    &mut update.borrow_mut(),
17526                    window,
17527                    cx,
17528                );
17529            })
17530            .detach();
17531        }
17532    });
17533
17534    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17535        (
17536            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17537            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17538        )
17539    });
17540
17541    // Insert some excerpts.
17542    leader.update(cx, |leader, cx| {
17543        leader.buffer.update(cx, |multibuffer, cx| {
17544            multibuffer.set_excerpts_for_path(
17545                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17546                buffer_1.clone(),
17547                vec![
17548                    Point::row_range(0..3),
17549                    Point::row_range(1..6),
17550                    Point::row_range(12..15),
17551                ],
17552                0,
17553                cx,
17554            );
17555            multibuffer.set_excerpts_for_path(
17556                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17557                buffer_2.clone(),
17558                vec![Point::row_range(0..6), Point::row_range(8..12)],
17559                0,
17560                cx,
17561            );
17562        });
17563    });
17564
17565    // Apply the update of adding the excerpts.
17566    follower_1
17567        .update_in(cx, |follower, window, cx| {
17568            follower.apply_update_proto(
17569                &project,
17570                update_message.borrow().clone().unwrap(),
17571                window,
17572                cx,
17573            )
17574        })
17575        .await
17576        .unwrap();
17577    assert_eq!(
17578        follower_1.update(cx, |editor, cx| editor.text(cx)),
17579        leader.update(cx, |editor, cx| editor.text(cx))
17580    );
17581    update_message.borrow_mut().take();
17582
17583    // Start following separately after it already has excerpts.
17584    let mut state_message =
17585        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17586    let workspace_entity = workspace.root(cx).unwrap();
17587    let follower_2 = cx
17588        .update_window(*workspace.deref(), |_, window, cx| {
17589            Editor::from_state_proto(
17590                workspace_entity,
17591                ViewId {
17592                    creator: CollaboratorId::PeerId(PeerId::default()),
17593                    id: 0,
17594                },
17595                &mut state_message,
17596                window,
17597                cx,
17598            )
17599        })
17600        .unwrap()
17601        .unwrap()
17602        .await
17603        .unwrap();
17604    assert_eq!(
17605        follower_2.update(cx, |editor, cx| editor.text(cx)),
17606        leader.update(cx, |editor, cx| editor.text(cx))
17607    );
17608
17609    // Remove some excerpts.
17610    leader.update(cx, |leader, cx| {
17611        leader.buffer.update(cx, |multibuffer, cx| {
17612            let excerpt_ids = multibuffer.excerpt_ids();
17613            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17614            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17615        });
17616    });
17617
17618    // Apply the update of removing the excerpts.
17619    follower_1
17620        .update_in(cx, |follower, window, cx| {
17621            follower.apply_update_proto(
17622                &project,
17623                update_message.borrow().clone().unwrap(),
17624                window,
17625                cx,
17626            )
17627        })
17628        .await
17629        .unwrap();
17630    follower_2
17631        .update_in(cx, |follower, window, cx| {
17632            follower.apply_update_proto(
17633                &project,
17634                update_message.borrow().clone().unwrap(),
17635                window,
17636                cx,
17637            )
17638        })
17639        .await
17640        .unwrap();
17641    update_message.borrow_mut().take();
17642    assert_eq!(
17643        follower_1.update(cx, |editor, cx| editor.text(cx)),
17644        leader.update(cx, |editor, cx| editor.text(cx))
17645    );
17646}
17647
17648#[gpui::test]
17649async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17650    init_test(cx, |_| {});
17651
17652    let mut cx = EditorTestContext::new(cx).await;
17653    let lsp_store =
17654        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17655
17656    cx.set_state(indoc! {"
17657        ˇfn func(abc def: i32) -> u32 {
17658        }
17659    "});
17660
17661    cx.update(|_, cx| {
17662        lsp_store.update(cx, |lsp_store, cx| {
17663            lsp_store
17664                .update_diagnostics(
17665                    LanguageServerId(0),
17666                    lsp::PublishDiagnosticsParams {
17667                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17668                        version: None,
17669                        diagnostics: vec![
17670                            lsp::Diagnostic {
17671                                range: lsp::Range::new(
17672                                    lsp::Position::new(0, 11),
17673                                    lsp::Position::new(0, 12),
17674                                ),
17675                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17676                                ..Default::default()
17677                            },
17678                            lsp::Diagnostic {
17679                                range: lsp::Range::new(
17680                                    lsp::Position::new(0, 12),
17681                                    lsp::Position::new(0, 15),
17682                                ),
17683                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17684                                ..Default::default()
17685                            },
17686                            lsp::Diagnostic {
17687                                range: lsp::Range::new(
17688                                    lsp::Position::new(0, 25),
17689                                    lsp::Position::new(0, 28),
17690                                ),
17691                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17692                                ..Default::default()
17693                            },
17694                        ],
17695                    },
17696                    None,
17697                    DiagnosticSourceKind::Pushed,
17698                    &[],
17699                    cx,
17700                )
17701                .unwrap()
17702        });
17703    });
17704
17705    executor.run_until_parked();
17706
17707    cx.update_editor(|editor, window, cx| {
17708        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17709    });
17710
17711    cx.assert_editor_state(indoc! {"
17712        fn func(abc def: i32) -> ˇu32 {
17713        }
17714    "});
17715
17716    cx.update_editor(|editor, window, cx| {
17717        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17718    });
17719
17720    cx.assert_editor_state(indoc! {"
17721        fn func(abc ˇdef: i32) -> u32 {
17722        }
17723    "});
17724
17725    cx.update_editor(|editor, window, cx| {
17726        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17727    });
17728
17729    cx.assert_editor_state(indoc! {"
17730        fn func(abcˇ def: i32) -> u32 {
17731        }
17732    "});
17733
17734    cx.update_editor(|editor, window, cx| {
17735        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17736    });
17737
17738    cx.assert_editor_state(indoc! {"
17739        fn func(abc def: i32) -> ˇu32 {
17740        }
17741    "});
17742}
17743
17744#[gpui::test]
17745async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17746    init_test(cx, |_| {});
17747
17748    let mut cx = EditorTestContext::new(cx).await;
17749
17750    let diff_base = r#"
17751        use some::mod;
17752
17753        const A: u32 = 42;
17754
17755        fn main() {
17756            println!("hello");
17757
17758            println!("world");
17759        }
17760        "#
17761    .unindent();
17762
17763    // Edits are modified, removed, modified, added
17764    cx.set_state(
17765        &r#"
17766        use some::modified;
17767
17768        ˇ
17769        fn main() {
17770            println!("hello there");
17771
17772            println!("around the");
17773            println!("world");
17774        }
17775        "#
17776        .unindent(),
17777    );
17778
17779    cx.set_head_text(&diff_base);
17780    executor.run_until_parked();
17781
17782    cx.update_editor(|editor, window, cx| {
17783        //Wrap around the bottom of the buffer
17784        for _ in 0..3 {
17785            editor.go_to_next_hunk(&GoToHunk, window, cx);
17786        }
17787    });
17788
17789    cx.assert_editor_state(
17790        &r#"
17791        ˇuse some::modified;
17792
17793
17794        fn main() {
17795            println!("hello there");
17796
17797            println!("around the");
17798            println!("world");
17799        }
17800        "#
17801        .unindent(),
17802    );
17803
17804    cx.update_editor(|editor, window, cx| {
17805        //Wrap around the top of the buffer
17806        for _ in 0..2 {
17807            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17808        }
17809    });
17810
17811    cx.assert_editor_state(
17812        &r#"
17813        use some::modified;
17814
17815
17816        fn main() {
17817        ˇ    println!("hello there");
17818
17819            println!("around the");
17820            println!("world");
17821        }
17822        "#
17823        .unindent(),
17824    );
17825
17826    cx.update_editor(|editor, window, cx| {
17827        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17828    });
17829
17830    cx.assert_editor_state(
17831        &r#"
17832        use some::modified;
17833
17834        ˇ
17835        fn main() {
17836            println!("hello there");
17837
17838            println!("around the");
17839            println!("world");
17840        }
17841        "#
17842        .unindent(),
17843    );
17844
17845    cx.update_editor(|editor, window, cx| {
17846        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17847    });
17848
17849    cx.assert_editor_state(
17850        &r#"
17851        ˇuse some::modified;
17852
17853
17854        fn main() {
17855            println!("hello there");
17856
17857            println!("around the");
17858            println!("world");
17859        }
17860        "#
17861        .unindent(),
17862    );
17863
17864    cx.update_editor(|editor, window, cx| {
17865        for _ in 0..2 {
17866            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17867        }
17868    });
17869
17870    cx.assert_editor_state(
17871        &r#"
17872        use some::modified;
17873
17874
17875        fn main() {
17876        ˇ    println!("hello there");
17877
17878            println!("around the");
17879            println!("world");
17880        }
17881        "#
17882        .unindent(),
17883    );
17884
17885    cx.update_editor(|editor, window, cx| {
17886        editor.fold(&Fold, window, cx);
17887    });
17888
17889    cx.update_editor(|editor, window, cx| {
17890        editor.go_to_next_hunk(&GoToHunk, window, cx);
17891    });
17892
17893    cx.assert_editor_state(
17894        &r#"
17895        ˇuse some::modified;
17896
17897
17898        fn main() {
17899            println!("hello there");
17900
17901            println!("around the");
17902            println!("world");
17903        }
17904        "#
17905        .unindent(),
17906    );
17907}
17908
17909#[test]
17910fn test_split_words() {
17911    fn split(text: &str) -> Vec<&str> {
17912        split_words(text).collect()
17913    }
17914
17915    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17916    assert_eq!(split("hello_world"), &["hello_", "world"]);
17917    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17918    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17919    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17920    assert_eq!(split("helloworld"), &["helloworld"]);
17921
17922    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17923}
17924
17925#[test]
17926fn test_split_words_for_snippet_prefix() {
17927    fn split(text: &str) -> Vec<&str> {
17928        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17929    }
17930
17931    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17932    assert_eq!(split("hello_world"), &["hello_world"]);
17933    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17934    assert_eq!(split("Hello_World"), &["Hello_World"]);
17935    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17936    assert_eq!(split("helloworld"), &["helloworld"]);
17937    assert_eq!(
17938        split("this@is!@#$^many   . symbols"),
17939        &[
17940            "symbols",
17941            " symbols",
17942            ". symbols",
17943            " . symbols",
17944            "  . symbols",
17945            "   . symbols",
17946            "many   . symbols",
17947            "^many   . symbols",
17948            "$^many   . symbols",
17949            "#$^many   . symbols",
17950            "@#$^many   . symbols",
17951            "!@#$^many   . symbols",
17952            "is!@#$^many   . symbols",
17953            "@is!@#$^many   . symbols",
17954            "this@is!@#$^many   . symbols",
17955        ],
17956    );
17957    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17958}
17959
17960#[gpui::test]
17961async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17962    init_test(cx, |_| {});
17963
17964    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17965
17966    #[track_caller]
17967    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17968        let _state_context = cx.set_state(before);
17969        cx.run_until_parked();
17970        cx.update_editor(|editor, window, cx| {
17971            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17972        });
17973        cx.run_until_parked();
17974        cx.assert_editor_state(after);
17975    }
17976
17977    // Outside bracket jumps to outside of matching bracket
17978    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17979    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17980
17981    // Inside bracket jumps to inside of matching bracket
17982    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17983    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17984
17985    // When outside a bracket and inside, favor jumping to the inside bracket
17986    assert(
17987        "console.log('foo', [1, 2, 3]ˇ);",
17988        "console.log('foo', ˇ[1, 2, 3]);",
17989        &mut cx,
17990    );
17991    assert(
17992        "console.log(ˇ'foo', [1, 2, 3]);",
17993        "console.log('foo'ˇ, [1, 2, 3]);",
17994        &mut cx,
17995    );
17996
17997    // Bias forward if two options are equally likely
17998    assert(
17999        "let result = curried_fun()ˇ();",
18000        "let result = curried_fun()()ˇ;",
18001        &mut cx,
18002    );
18003
18004    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
18005    assert(
18006        indoc! {"
18007            function test() {
18008                console.log('test')ˇ
18009            }"},
18010        indoc! {"
18011            function test() {
18012                console.logˇ('test')
18013            }"},
18014        &mut cx,
18015    );
18016}
18017
18018#[gpui::test]
18019async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
18020    init_test(cx, |_| {});
18021    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
18022    language_registry.add(markdown_lang());
18023    language_registry.add(rust_lang());
18024    let buffer = cx.new(|cx| {
18025        let mut buffer = language::Buffer::local(
18026            indoc! {"
18027            ```rs
18028            impl Worktree {
18029                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18030                }
18031            }
18032            ```
18033        "},
18034            cx,
18035        );
18036        buffer.set_language_registry(language_registry.clone());
18037        buffer.set_language(Some(markdown_lang()), cx);
18038        buffer
18039    });
18040    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18041    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
18042    cx.executor().run_until_parked();
18043    _ = editor.update(cx, |editor, window, cx| {
18044        // Case 1: Test outer enclosing brackets
18045        select_ranges(
18046            editor,
18047            &indoc! {"
18048                ```rs
18049                impl Worktree {
18050                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18051                    }
1805218053                ```
18054            "},
18055            window,
18056            cx,
18057        );
18058        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18059        assert_text_with_selections(
18060            editor,
18061            &indoc! {"
18062                ```rs
18063                impl Worktree ˇ{
18064                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
18065                    }
18066                }
18067                ```
18068            "},
18069            cx,
18070        );
18071        // Case 2: Test inner enclosing brackets
18072        select_ranges(
18073            editor,
18074            &indoc! {"
18075                ```rs
18076                impl Worktree {
18077                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1807818079                }
18080                ```
18081            "},
18082            window,
18083            cx,
18084        );
18085        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
18086        assert_text_with_selections(
18087            editor,
18088            &indoc! {"
18089                ```rs
18090                impl Worktree {
18091                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
18092                    }
18093                }
18094                ```
18095            "},
18096            cx,
18097        );
18098    });
18099}
18100
18101#[gpui::test]
18102async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
18103    init_test(cx, |_| {});
18104
18105    let fs = FakeFs::new(cx.executor());
18106    fs.insert_tree(
18107        path!("/a"),
18108        json!({
18109            "main.rs": "fn main() { let a = 5; }",
18110            "other.rs": "// Test file",
18111        }),
18112    )
18113    .await;
18114    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18115
18116    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18117    language_registry.add(Arc::new(Language::new(
18118        LanguageConfig {
18119            name: "Rust".into(),
18120            matcher: LanguageMatcher {
18121                path_suffixes: vec!["rs".to_string()],
18122                ..Default::default()
18123            },
18124            brackets: BracketPairConfig {
18125                pairs: vec![BracketPair {
18126                    start: "{".to_string(),
18127                    end: "}".to_string(),
18128                    close: true,
18129                    surround: true,
18130                    newline: true,
18131                }],
18132                disabled_scopes_by_bracket_ix: Vec::new(),
18133            },
18134            ..Default::default()
18135        },
18136        Some(tree_sitter_rust::LANGUAGE.into()),
18137    )));
18138    let mut fake_servers = language_registry.register_fake_lsp(
18139        "Rust",
18140        FakeLspAdapter {
18141            capabilities: lsp::ServerCapabilities {
18142                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18143                    first_trigger_character: "{".to_string(),
18144                    more_trigger_character: None,
18145                }),
18146                ..Default::default()
18147            },
18148            ..Default::default()
18149        },
18150    );
18151
18152    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18153
18154    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18155
18156    let worktree_id = workspace
18157        .update(cx, |workspace, _, cx| {
18158            workspace.project().update(cx, |project, cx| {
18159                project.worktrees(cx).next().unwrap().read(cx).id()
18160            })
18161        })
18162        .unwrap();
18163
18164    let buffer = project
18165        .update(cx, |project, cx| {
18166            project.open_local_buffer(path!("/a/main.rs"), cx)
18167        })
18168        .await
18169        .unwrap();
18170    let editor_handle = workspace
18171        .update(cx, |workspace, window, cx| {
18172            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18173        })
18174        .unwrap()
18175        .await
18176        .unwrap()
18177        .downcast::<Editor>()
18178        .unwrap();
18179
18180    cx.executor().start_waiting();
18181    let fake_server = fake_servers.next().await.unwrap();
18182
18183    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18184        |params, _| async move {
18185            assert_eq!(
18186                params.text_document_position.text_document.uri,
18187                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18188            );
18189            assert_eq!(
18190                params.text_document_position.position,
18191                lsp::Position::new(0, 21),
18192            );
18193
18194            Ok(Some(vec![lsp::TextEdit {
18195                new_text: "]".to_string(),
18196                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18197            }]))
18198        },
18199    );
18200
18201    editor_handle.update_in(cx, |editor, window, cx| {
18202        window.focus(&editor.focus_handle(cx));
18203        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18204            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18205        });
18206        editor.handle_input("{", window, cx);
18207    });
18208
18209    cx.executor().run_until_parked();
18210
18211    buffer.update(cx, |buffer, _| {
18212        assert_eq!(
18213            buffer.text(),
18214            "fn main() { let a = {5}; }",
18215            "No extra braces from on type formatting should appear in the buffer"
18216        )
18217    });
18218}
18219
18220#[gpui::test(iterations = 20, seeds(31))]
18221async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18222    init_test(cx, |_| {});
18223
18224    let mut cx = EditorLspTestContext::new_rust(
18225        lsp::ServerCapabilities {
18226            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18227                first_trigger_character: ".".to_string(),
18228                more_trigger_character: None,
18229            }),
18230            ..Default::default()
18231        },
18232        cx,
18233    )
18234    .await;
18235
18236    cx.update_buffer(|buffer, _| {
18237        // This causes autoindent to be async.
18238        buffer.set_sync_parse_timeout(Duration::ZERO)
18239    });
18240
18241    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18242    cx.simulate_keystroke("\n");
18243    cx.run_until_parked();
18244
18245    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18246    let mut request =
18247        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18248            let buffer_cloned = buffer_cloned.clone();
18249            async move {
18250                buffer_cloned.update(&mut cx, |buffer, _| {
18251                    assert_eq!(
18252                        buffer.text(),
18253                        "fn c() {\n    d()\n        .\n}\n",
18254                        "OnTypeFormatting should triggered after autoindent applied"
18255                    )
18256                })?;
18257
18258                Ok(Some(vec![]))
18259            }
18260        });
18261
18262    cx.simulate_keystroke(".");
18263    cx.run_until_parked();
18264
18265    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18266    assert!(request.next().await.is_some());
18267    request.close();
18268    assert!(request.next().await.is_none());
18269}
18270
18271#[gpui::test]
18272async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18273    init_test(cx, |_| {});
18274
18275    let fs = FakeFs::new(cx.executor());
18276    fs.insert_tree(
18277        path!("/a"),
18278        json!({
18279            "main.rs": "fn main() { let a = 5; }",
18280            "other.rs": "// Test file",
18281        }),
18282    )
18283    .await;
18284
18285    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18286
18287    let server_restarts = Arc::new(AtomicUsize::new(0));
18288    let closure_restarts = Arc::clone(&server_restarts);
18289    let language_server_name = "test language server";
18290    let language_name: LanguageName = "Rust".into();
18291
18292    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18293    language_registry.add(Arc::new(Language::new(
18294        LanguageConfig {
18295            name: language_name.clone(),
18296            matcher: LanguageMatcher {
18297                path_suffixes: vec!["rs".to_string()],
18298                ..Default::default()
18299            },
18300            ..Default::default()
18301        },
18302        Some(tree_sitter_rust::LANGUAGE.into()),
18303    )));
18304    let mut fake_servers = language_registry.register_fake_lsp(
18305        "Rust",
18306        FakeLspAdapter {
18307            name: language_server_name,
18308            initialization_options: Some(json!({
18309                "testOptionValue": true
18310            })),
18311            initializer: Some(Box::new(move |fake_server| {
18312                let task_restarts = Arc::clone(&closure_restarts);
18313                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18314                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18315                    futures::future::ready(Ok(()))
18316                });
18317            })),
18318            ..Default::default()
18319        },
18320    );
18321
18322    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18323    let _buffer = project
18324        .update(cx, |project, cx| {
18325            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18326        })
18327        .await
18328        .unwrap();
18329    let _fake_server = fake_servers.next().await.unwrap();
18330    update_test_language_settings(cx, |language_settings| {
18331        language_settings.languages.0.insert(
18332            language_name.clone().0,
18333            LanguageSettingsContent {
18334                tab_size: NonZeroU32::new(8),
18335                ..Default::default()
18336            },
18337        );
18338    });
18339    cx.executor().run_until_parked();
18340    assert_eq!(
18341        server_restarts.load(atomic::Ordering::Acquire),
18342        0,
18343        "Should not restart LSP server on an unrelated change"
18344    );
18345
18346    update_test_project_settings(cx, |project_settings| {
18347        project_settings.lsp.insert(
18348            "Some other server name".into(),
18349            LspSettings {
18350                binary: None,
18351                settings: None,
18352                initialization_options: Some(json!({
18353                    "some other init value": false
18354                })),
18355                enable_lsp_tasks: false,
18356                fetch: None,
18357            },
18358        );
18359    });
18360    cx.executor().run_until_parked();
18361    assert_eq!(
18362        server_restarts.load(atomic::Ordering::Acquire),
18363        0,
18364        "Should not restart LSP server on an unrelated LSP settings change"
18365    );
18366
18367    update_test_project_settings(cx, |project_settings| {
18368        project_settings.lsp.insert(
18369            language_server_name.into(),
18370            LspSettings {
18371                binary: None,
18372                settings: None,
18373                initialization_options: Some(json!({
18374                    "anotherInitValue": false
18375                })),
18376                enable_lsp_tasks: false,
18377                fetch: None,
18378            },
18379        );
18380    });
18381    cx.executor().run_until_parked();
18382    assert_eq!(
18383        server_restarts.load(atomic::Ordering::Acquire),
18384        1,
18385        "Should restart LSP server on a related LSP settings change"
18386    );
18387
18388    update_test_project_settings(cx, |project_settings| {
18389        project_settings.lsp.insert(
18390            language_server_name.into(),
18391            LspSettings {
18392                binary: None,
18393                settings: None,
18394                initialization_options: Some(json!({
18395                    "anotherInitValue": false
18396                })),
18397                enable_lsp_tasks: false,
18398                fetch: None,
18399            },
18400        );
18401    });
18402    cx.executor().run_until_parked();
18403    assert_eq!(
18404        server_restarts.load(atomic::Ordering::Acquire),
18405        1,
18406        "Should not restart LSP server on a related LSP settings change that is the same"
18407    );
18408
18409    update_test_project_settings(cx, |project_settings| {
18410        project_settings.lsp.insert(
18411            language_server_name.into(),
18412            LspSettings {
18413                binary: None,
18414                settings: None,
18415                initialization_options: None,
18416                enable_lsp_tasks: false,
18417                fetch: None,
18418            },
18419        );
18420    });
18421    cx.executor().run_until_parked();
18422    assert_eq!(
18423        server_restarts.load(atomic::Ordering::Acquire),
18424        2,
18425        "Should restart LSP server on another related LSP settings change"
18426    );
18427}
18428
18429#[gpui::test]
18430async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18431    init_test(cx, |_| {});
18432
18433    let mut cx = EditorLspTestContext::new_rust(
18434        lsp::ServerCapabilities {
18435            completion_provider: Some(lsp::CompletionOptions {
18436                trigger_characters: Some(vec![".".to_string()]),
18437                resolve_provider: Some(true),
18438                ..Default::default()
18439            }),
18440            ..Default::default()
18441        },
18442        cx,
18443    )
18444    .await;
18445
18446    cx.set_state("fn main() { let a = 2ˇ; }");
18447    cx.simulate_keystroke(".");
18448    let completion_item = lsp::CompletionItem {
18449        label: "some".into(),
18450        kind: Some(lsp::CompletionItemKind::SNIPPET),
18451        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18452        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18453            kind: lsp::MarkupKind::Markdown,
18454            value: "```rust\nSome(2)\n```".to_string(),
18455        })),
18456        deprecated: Some(false),
18457        sort_text: Some("fffffff2".to_string()),
18458        filter_text: Some("some".to_string()),
18459        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18460        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18461            range: lsp::Range {
18462                start: lsp::Position {
18463                    line: 0,
18464                    character: 22,
18465                },
18466                end: lsp::Position {
18467                    line: 0,
18468                    character: 22,
18469                },
18470            },
18471            new_text: "Some(2)".to_string(),
18472        })),
18473        additional_text_edits: Some(vec![lsp::TextEdit {
18474            range: lsp::Range {
18475                start: lsp::Position {
18476                    line: 0,
18477                    character: 20,
18478                },
18479                end: lsp::Position {
18480                    line: 0,
18481                    character: 22,
18482                },
18483            },
18484            new_text: "".to_string(),
18485        }]),
18486        ..Default::default()
18487    };
18488
18489    let closure_completion_item = completion_item.clone();
18490    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18491        let task_completion_item = closure_completion_item.clone();
18492        async move {
18493            Ok(Some(lsp::CompletionResponse::Array(vec![
18494                task_completion_item,
18495            ])))
18496        }
18497    });
18498
18499    request.next().await;
18500
18501    cx.condition(|editor, _| editor.context_menu_visible())
18502        .await;
18503    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18504        editor
18505            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18506            .unwrap()
18507    });
18508    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18509
18510    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18511        let task_completion_item = completion_item.clone();
18512        async move { Ok(task_completion_item) }
18513    })
18514    .next()
18515    .await
18516    .unwrap();
18517    apply_additional_edits.await.unwrap();
18518    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18519}
18520
18521#[gpui::test]
18522async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18523    init_test(cx, |_| {});
18524
18525    let mut cx = EditorLspTestContext::new_rust(
18526        lsp::ServerCapabilities {
18527            completion_provider: Some(lsp::CompletionOptions {
18528                trigger_characters: Some(vec![".".to_string()]),
18529                resolve_provider: Some(true),
18530                ..Default::default()
18531            }),
18532            ..Default::default()
18533        },
18534        cx,
18535    )
18536    .await;
18537
18538    cx.set_state("fn main() { let a = 2ˇ; }");
18539    cx.simulate_keystroke(".");
18540
18541    let item1 = lsp::CompletionItem {
18542        label: "method id()".to_string(),
18543        filter_text: Some("id".to_string()),
18544        detail: None,
18545        documentation: None,
18546        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18547            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18548            new_text: ".id".to_string(),
18549        })),
18550        ..lsp::CompletionItem::default()
18551    };
18552
18553    let item2 = lsp::CompletionItem {
18554        label: "other".to_string(),
18555        filter_text: Some("other".to_string()),
18556        detail: None,
18557        documentation: None,
18558        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18559            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18560            new_text: ".other".to_string(),
18561        })),
18562        ..lsp::CompletionItem::default()
18563    };
18564
18565    let item1 = item1.clone();
18566    cx.set_request_handler::<lsp::request::Completion, _, _>({
18567        let item1 = item1.clone();
18568        move |_, _, _| {
18569            let item1 = item1.clone();
18570            let item2 = item2.clone();
18571            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18572        }
18573    })
18574    .next()
18575    .await;
18576
18577    cx.condition(|editor, _| editor.context_menu_visible())
18578        .await;
18579    cx.update_editor(|editor, _, _| {
18580        let context_menu = editor.context_menu.borrow_mut();
18581        let context_menu = context_menu
18582            .as_ref()
18583            .expect("Should have the context menu deployed");
18584        match context_menu {
18585            CodeContextMenu::Completions(completions_menu) => {
18586                let completions = completions_menu.completions.borrow_mut();
18587                assert_eq!(
18588                    completions
18589                        .iter()
18590                        .map(|completion| &completion.label.text)
18591                        .collect::<Vec<_>>(),
18592                    vec!["method id()", "other"]
18593                )
18594            }
18595            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18596        }
18597    });
18598
18599    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18600        let item1 = item1.clone();
18601        move |_, item_to_resolve, _| {
18602            let item1 = item1.clone();
18603            async move {
18604                if item1 == item_to_resolve {
18605                    Ok(lsp::CompletionItem {
18606                        label: "method id()".to_string(),
18607                        filter_text: Some("id".to_string()),
18608                        detail: Some("Now resolved!".to_string()),
18609                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18610                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18611                            range: lsp::Range::new(
18612                                lsp::Position::new(0, 22),
18613                                lsp::Position::new(0, 22),
18614                            ),
18615                            new_text: ".id".to_string(),
18616                        })),
18617                        ..lsp::CompletionItem::default()
18618                    })
18619                } else {
18620                    Ok(item_to_resolve)
18621                }
18622            }
18623        }
18624    })
18625    .next()
18626    .await
18627    .unwrap();
18628    cx.run_until_parked();
18629
18630    cx.update_editor(|editor, window, cx| {
18631        editor.context_menu_next(&Default::default(), window, cx);
18632    });
18633
18634    cx.update_editor(|editor, _, _| {
18635        let context_menu = editor.context_menu.borrow_mut();
18636        let context_menu = context_menu
18637            .as_ref()
18638            .expect("Should have the context menu deployed");
18639        match context_menu {
18640            CodeContextMenu::Completions(completions_menu) => {
18641                let completions = completions_menu.completions.borrow_mut();
18642                assert_eq!(
18643                    completions
18644                        .iter()
18645                        .map(|completion| &completion.label.text)
18646                        .collect::<Vec<_>>(),
18647                    vec!["method id() Now resolved!", "other"],
18648                    "Should update first completion label, but not second as the filter text did not match."
18649                );
18650            }
18651            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18652        }
18653    });
18654}
18655
18656#[gpui::test]
18657async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18658    init_test(cx, |_| {});
18659    let mut cx = EditorLspTestContext::new_rust(
18660        lsp::ServerCapabilities {
18661            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18662            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18663            completion_provider: Some(lsp::CompletionOptions {
18664                resolve_provider: Some(true),
18665                ..Default::default()
18666            }),
18667            ..Default::default()
18668        },
18669        cx,
18670    )
18671    .await;
18672    cx.set_state(indoc! {"
18673        struct TestStruct {
18674            field: i32
18675        }
18676
18677        fn mainˇ() {
18678            let unused_var = 42;
18679            let test_struct = TestStruct { field: 42 };
18680        }
18681    "});
18682    let symbol_range = cx.lsp_range(indoc! {"
18683        struct TestStruct {
18684            field: i32
18685        }
18686
18687        «fn main»() {
18688            let unused_var = 42;
18689            let test_struct = TestStruct { field: 42 };
18690        }
18691    "});
18692    let mut hover_requests =
18693        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18694            Ok(Some(lsp::Hover {
18695                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18696                    kind: lsp::MarkupKind::Markdown,
18697                    value: "Function documentation".to_string(),
18698                }),
18699                range: Some(symbol_range),
18700            }))
18701        });
18702
18703    // Case 1: Test that code action menu hide hover popover
18704    cx.dispatch_action(Hover);
18705    hover_requests.next().await;
18706    cx.condition(|editor, _| editor.hover_state.visible()).await;
18707    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18708        move |_, _, _| async move {
18709            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18710                lsp::CodeAction {
18711                    title: "Remove unused variable".to_string(),
18712                    kind: Some(CodeActionKind::QUICKFIX),
18713                    edit: Some(lsp::WorkspaceEdit {
18714                        changes: Some(
18715                            [(
18716                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18717                                vec![lsp::TextEdit {
18718                                    range: lsp::Range::new(
18719                                        lsp::Position::new(5, 4),
18720                                        lsp::Position::new(5, 27),
18721                                    ),
18722                                    new_text: "".to_string(),
18723                                }],
18724                            )]
18725                            .into_iter()
18726                            .collect(),
18727                        ),
18728                        ..Default::default()
18729                    }),
18730                    ..Default::default()
18731                },
18732            )]))
18733        },
18734    );
18735    cx.update_editor(|editor, window, cx| {
18736        editor.toggle_code_actions(
18737            &ToggleCodeActions {
18738                deployed_from: None,
18739                quick_launch: false,
18740            },
18741            window,
18742            cx,
18743        );
18744    });
18745    code_action_requests.next().await;
18746    cx.run_until_parked();
18747    cx.condition(|editor, _| editor.context_menu_visible())
18748        .await;
18749    cx.update_editor(|editor, _, _| {
18750        assert!(
18751            !editor.hover_state.visible(),
18752            "Hover popover should be hidden when code action menu is shown"
18753        );
18754        // Hide code actions
18755        editor.context_menu.take();
18756    });
18757
18758    // Case 2: Test that code completions hide hover popover
18759    cx.dispatch_action(Hover);
18760    hover_requests.next().await;
18761    cx.condition(|editor, _| editor.hover_state.visible()).await;
18762    let counter = Arc::new(AtomicUsize::new(0));
18763    let mut completion_requests =
18764        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18765            let counter = counter.clone();
18766            async move {
18767                counter.fetch_add(1, atomic::Ordering::Release);
18768                Ok(Some(lsp::CompletionResponse::Array(vec![
18769                    lsp::CompletionItem {
18770                        label: "main".into(),
18771                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18772                        detail: Some("() -> ()".to_string()),
18773                        ..Default::default()
18774                    },
18775                    lsp::CompletionItem {
18776                        label: "TestStruct".into(),
18777                        kind: Some(lsp::CompletionItemKind::STRUCT),
18778                        detail: Some("struct TestStruct".to_string()),
18779                        ..Default::default()
18780                    },
18781                ])))
18782            }
18783        });
18784    cx.update_editor(|editor, window, cx| {
18785        editor.show_completions(&ShowCompletions, window, cx);
18786    });
18787    completion_requests.next().await;
18788    cx.condition(|editor, _| editor.context_menu_visible())
18789        .await;
18790    cx.update_editor(|editor, _, _| {
18791        assert!(
18792            !editor.hover_state.visible(),
18793            "Hover popover should be hidden when completion menu is shown"
18794        );
18795    });
18796}
18797
18798#[gpui::test]
18799async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18800    init_test(cx, |_| {});
18801
18802    let mut cx = EditorLspTestContext::new_rust(
18803        lsp::ServerCapabilities {
18804            completion_provider: Some(lsp::CompletionOptions {
18805                trigger_characters: Some(vec![".".to_string()]),
18806                resolve_provider: Some(true),
18807                ..Default::default()
18808            }),
18809            ..Default::default()
18810        },
18811        cx,
18812    )
18813    .await;
18814
18815    cx.set_state("fn main() { let a = 2ˇ; }");
18816    cx.simulate_keystroke(".");
18817
18818    let unresolved_item_1 = lsp::CompletionItem {
18819        label: "id".to_string(),
18820        filter_text: Some("id".to_string()),
18821        detail: None,
18822        documentation: None,
18823        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18824            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18825            new_text: ".id".to_string(),
18826        })),
18827        ..lsp::CompletionItem::default()
18828    };
18829    let resolved_item_1 = lsp::CompletionItem {
18830        additional_text_edits: Some(vec![lsp::TextEdit {
18831            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18832            new_text: "!!".to_string(),
18833        }]),
18834        ..unresolved_item_1.clone()
18835    };
18836    let unresolved_item_2 = lsp::CompletionItem {
18837        label: "other".to_string(),
18838        filter_text: Some("other".to_string()),
18839        detail: None,
18840        documentation: None,
18841        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18842            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18843            new_text: ".other".to_string(),
18844        })),
18845        ..lsp::CompletionItem::default()
18846    };
18847    let resolved_item_2 = lsp::CompletionItem {
18848        additional_text_edits: Some(vec![lsp::TextEdit {
18849            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18850            new_text: "??".to_string(),
18851        }]),
18852        ..unresolved_item_2.clone()
18853    };
18854
18855    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18856    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18857    cx.lsp
18858        .server
18859        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18860            let unresolved_item_1 = unresolved_item_1.clone();
18861            let resolved_item_1 = resolved_item_1.clone();
18862            let unresolved_item_2 = unresolved_item_2.clone();
18863            let resolved_item_2 = resolved_item_2.clone();
18864            let resolve_requests_1 = resolve_requests_1.clone();
18865            let resolve_requests_2 = resolve_requests_2.clone();
18866            move |unresolved_request, _| {
18867                let unresolved_item_1 = unresolved_item_1.clone();
18868                let resolved_item_1 = resolved_item_1.clone();
18869                let unresolved_item_2 = unresolved_item_2.clone();
18870                let resolved_item_2 = resolved_item_2.clone();
18871                let resolve_requests_1 = resolve_requests_1.clone();
18872                let resolve_requests_2 = resolve_requests_2.clone();
18873                async move {
18874                    if unresolved_request == unresolved_item_1 {
18875                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18876                        Ok(resolved_item_1.clone())
18877                    } else if unresolved_request == unresolved_item_2 {
18878                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18879                        Ok(resolved_item_2.clone())
18880                    } else {
18881                        panic!("Unexpected completion item {unresolved_request:?}")
18882                    }
18883                }
18884            }
18885        })
18886        .detach();
18887
18888    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18889        let unresolved_item_1 = unresolved_item_1.clone();
18890        let unresolved_item_2 = unresolved_item_2.clone();
18891        async move {
18892            Ok(Some(lsp::CompletionResponse::Array(vec![
18893                unresolved_item_1,
18894                unresolved_item_2,
18895            ])))
18896        }
18897    })
18898    .next()
18899    .await;
18900
18901    cx.condition(|editor, _| editor.context_menu_visible())
18902        .await;
18903    cx.update_editor(|editor, _, _| {
18904        let context_menu = editor.context_menu.borrow_mut();
18905        let context_menu = context_menu
18906            .as_ref()
18907            .expect("Should have the context menu deployed");
18908        match context_menu {
18909            CodeContextMenu::Completions(completions_menu) => {
18910                let completions = completions_menu.completions.borrow_mut();
18911                assert_eq!(
18912                    completions
18913                        .iter()
18914                        .map(|completion| &completion.label.text)
18915                        .collect::<Vec<_>>(),
18916                    vec!["id", "other"]
18917                )
18918            }
18919            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18920        }
18921    });
18922    cx.run_until_parked();
18923
18924    cx.update_editor(|editor, window, cx| {
18925        editor.context_menu_next(&ContextMenuNext, window, cx);
18926    });
18927    cx.run_until_parked();
18928    cx.update_editor(|editor, window, cx| {
18929        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18930    });
18931    cx.run_until_parked();
18932    cx.update_editor(|editor, window, cx| {
18933        editor.context_menu_next(&ContextMenuNext, window, cx);
18934    });
18935    cx.run_until_parked();
18936    cx.update_editor(|editor, window, cx| {
18937        editor
18938            .compose_completion(&ComposeCompletion::default(), window, cx)
18939            .expect("No task returned")
18940    })
18941    .await
18942    .expect("Completion failed");
18943    cx.run_until_parked();
18944
18945    cx.update_editor(|editor, _, cx| {
18946        assert_eq!(
18947            resolve_requests_1.load(atomic::Ordering::Acquire),
18948            1,
18949            "Should always resolve once despite multiple selections"
18950        );
18951        assert_eq!(
18952            resolve_requests_2.load(atomic::Ordering::Acquire),
18953            1,
18954            "Should always resolve once after multiple selections and applying the completion"
18955        );
18956        assert_eq!(
18957            editor.text(cx),
18958            "fn main() { let a = ??.other; }",
18959            "Should use resolved data when applying the completion"
18960        );
18961    });
18962}
18963
18964#[gpui::test]
18965async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18966    init_test(cx, |_| {});
18967
18968    let item_0 = lsp::CompletionItem {
18969        label: "abs".into(),
18970        insert_text: Some("abs".into()),
18971        data: Some(json!({ "very": "special"})),
18972        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18973        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18974            lsp::InsertReplaceEdit {
18975                new_text: "abs".to_string(),
18976                insert: lsp::Range::default(),
18977                replace: lsp::Range::default(),
18978            },
18979        )),
18980        ..lsp::CompletionItem::default()
18981    };
18982    let items = iter::once(item_0.clone())
18983        .chain((11..51).map(|i| lsp::CompletionItem {
18984            label: format!("item_{}", i),
18985            insert_text: Some(format!("item_{}", i)),
18986            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18987            ..lsp::CompletionItem::default()
18988        }))
18989        .collect::<Vec<_>>();
18990
18991    let default_commit_characters = vec!["?".to_string()];
18992    let default_data = json!({ "default": "data"});
18993    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18994    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18995    let default_edit_range = lsp::Range {
18996        start: lsp::Position {
18997            line: 0,
18998            character: 5,
18999        },
19000        end: lsp::Position {
19001            line: 0,
19002            character: 5,
19003        },
19004    };
19005
19006    let mut cx = EditorLspTestContext::new_rust(
19007        lsp::ServerCapabilities {
19008            completion_provider: Some(lsp::CompletionOptions {
19009                trigger_characters: Some(vec![".".to_string()]),
19010                resolve_provider: Some(true),
19011                ..Default::default()
19012            }),
19013            ..Default::default()
19014        },
19015        cx,
19016    )
19017    .await;
19018
19019    cx.set_state("fn main() { let a = 2ˇ; }");
19020    cx.simulate_keystroke(".");
19021
19022    let completion_data = default_data.clone();
19023    let completion_characters = default_commit_characters.clone();
19024    let completion_items = items.clone();
19025    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
19026        let default_data = completion_data.clone();
19027        let default_commit_characters = completion_characters.clone();
19028        let items = completion_items.clone();
19029        async move {
19030            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
19031                items,
19032                item_defaults: Some(lsp::CompletionListItemDefaults {
19033                    data: Some(default_data.clone()),
19034                    commit_characters: Some(default_commit_characters.clone()),
19035                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
19036                        default_edit_range,
19037                    )),
19038                    insert_text_format: Some(default_insert_text_format),
19039                    insert_text_mode: Some(default_insert_text_mode),
19040                }),
19041                ..lsp::CompletionList::default()
19042            })))
19043        }
19044    })
19045    .next()
19046    .await;
19047
19048    let resolved_items = Arc::new(Mutex::new(Vec::new()));
19049    cx.lsp
19050        .server
19051        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
19052            let closure_resolved_items = resolved_items.clone();
19053            move |item_to_resolve, _| {
19054                let closure_resolved_items = closure_resolved_items.clone();
19055                async move {
19056                    closure_resolved_items.lock().push(item_to_resolve.clone());
19057                    Ok(item_to_resolve)
19058                }
19059            }
19060        })
19061        .detach();
19062
19063    cx.condition(|editor, _| editor.context_menu_visible())
19064        .await;
19065    cx.run_until_parked();
19066    cx.update_editor(|editor, _, _| {
19067        let menu = editor.context_menu.borrow_mut();
19068        match menu.as_ref().expect("should have the completions menu") {
19069            CodeContextMenu::Completions(completions_menu) => {
19070                assert_eq!(
19071                    completions_menu
19072                        .entries
19073                        .borrow()
19074                        .iter()
19075                        .map(|mat| mat.string.clone())
19076                        .collect::<Vec<String>>(),
19077                    items
19078                        .iter()
19079                        .map(|completion| completion.label.clone())
19080                        .collect::<Vec<String>>()
19081                );
19082            }
19083            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
19084        }
19085    });
19086    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
19087    // with 4 from the end.
19088    assert_eq!(
19089        *resolved_items.lock(),
19090        [&items[0..16], &items[items.len() - 4..items.len()]]
19091            .concat()
19092            .iter()
19093            .cloned()
19094            .map(|mut item| {
19095                if item.data.is_none() {
19096                    item.data = Some(default_data.clone());
19097                }
19098                item
19099            })
19100            .collect::<Vec<lsp::CompletionItem>>(),
19101        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
19102    );
19103    resolved_items.lock().clear();
19104
19105    cx.update_editor(|editor, window, cx| {
19106        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
19107    });
19108    cx.run_until_parked();
19109    // Completions that have already been resolved are skipped.
19110    assert_eq!(
19111        *resolved_items.lock(),
19112        items[items.len() - 17..items.len() - 4]
19113            .iter()
19114            .cloned()
19115            .map(|mut item| {
19116                if item.data.is_none() {
19117                    item.data = Some(default_data.clone());
19118                }
19119                item
19120            })
19121            .collect::<Vec<lsp::CompletionItem>>()
19122    );
19123    resolved_items.lock().clear();
19124}
19125
19126#[gpui::test]
19127async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19128    init_test(cx, |_| {});
19129
19130    let mut cx = EditorLspTestContext::new(
19131        Language::new(
19132            LanguageConfig {
19133                matcher: LanguageMatcher {
19134                    path_suffixes: vec!["jsx".into()],
19135                    ..Default::default()
19136                },
19137                overrides: [(
19138                    "element".into(),
19139                    LanguageConfigOverride {
19140                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19141                        ..Default::default()
19142                    },
19143                )]
19144                .into_iter()
19145                .collect(),
19146                ..Default::default()
19147            },
19148            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19149        )
19150        .with_override_query("(jsx_self_closing_element) @element")
19151        .unwrap(),
19152        lsp::ServerCapabilities {
19153            completion_provider: Some(lsp::CompletionOptions {
19154                trigger_characters: Some(vec![":".to_string()]),
19155                ..Default::default()
19156            }),
19157            ..Default::default()
19158        },
19159        cx,
19160    )
19161    .await;
19162
19163    cx.lsp
19164        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19165            Ok(Some(lsp::CompletionResponse::Array(vec![
19166                lsp::CompletionItem {
19167                    label: "bg-blue".into(),
19168                    ..Default::default()
19169                },
19170                lsp::CompletionItem {
19171                    label: "bg-red".into(),
19172                    ..Default::default()
19173                },
19174                lsp::CompletionItem {
19175                    label: "bg-yellow".into(),
19176                    ..Default::default()
19177                },
19178            ])))
19179        });
19180
19181    cx.set_state(r#"<p class="bgˇ" />"#);
19182
19183    // Trigger completion when typing a dash, because the dash is an extra
19184    // word character in the 'element' scope, which contains the cursor.
19185    cx.simulate_keystroke("-");
19186    cx.executor().run_until_parked();
19187    cx.update_editor(|editor, _, _| {
19188        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19189        {
19190            assert_eq!(
19191                completion_menu_entries(menu),
19192                &["bg-blue", "bg-red", "bg-yellow"]
19193            );
19194        } else {
19195            panic!("expected completion menu to be open");
19196        }
19197    });
19198
19199    cx.simulate_keystroke("l");
19200    cx.executor().run_until_parked();
19201    cx.update_editor(|editor, _, _| {
19202        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19203        {
19204            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19205        } else {
19206            panic!("expected completion menu to be open");
19207        }
19208    });
19209
19210    // When filtering completions, consider the character after the '-' to
19211    // be the start of a subword.
19212    cx.set_state(r#"<p class="yelˇ" />"#);
19213    cx.simulate_keystroke("l");
19214    cx.executor().run_until_parked();
19215    cx.update_editor(|editor, _, _| {
19216        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19217        {
19218            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19219        } else {
19220            panic!("expected completion menu to be open");
19221        }
19222    });
19223}
19224
19225fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19226    let entries = menu.entries.borrow();
19227    entries.iter().map(|mat| mat.string.clone()).collect()
19228}
19229
19230#[gpui::test]
19231async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19232    init_test(cx, |settings| {
19233        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19234    });
19235
19236    let fs = FakeFs::new(cx.executor());
19237    fs.insert_file(path!("/file.ts"), Default::default()).await;
19238
19239    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19240    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19241
19242    language_registry.add(Arc::new(Language::new(
19243        LanguageConfig {
19244            name: "TypeScript".into(),
19245            matcher: LanguageMatcher {
19246                path_suffixes: vec!["ts".to_string()],
19247                ..Default::default()
19248            },
19249            ..Default::default()
19250        },
19251        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19252    )));
19253    update_test_language_settings(cx, |settings| {
19254        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19255    });
19256
19257    let test_plugin = "test_plugin";
19258    let _ = language_registry.register_fake_lsp(
19259        "TypeScript",
19260        FakeLspAdapter {
19261            prettier_plugins: vec![test_plugin],
19262            ..Default::default()
19263        },
19264    );
19265
19266    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19267    let buffer = project
19268        .update(cx, |project, cx| {
19269            project.open_local_buffer(path!("/file.ts"), cx)
19270        })
19271        .await
19272        .unwrap();
19273
19274    let buffer_text = "one\ntwo\nthree\n";
19275    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19276    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19277    editor.update_in(cx, |editor, window, cx| {
19278        editor.set_text(buffer_text, window, cx)
19279    });
19280
19281    editor
19282        .update_in(cx, |editor, window, cx| {
19283            editor.perform_format(
19284                project.clone(),
19285                FormatTrigger::Manual,
19286                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19287                window,
19288                cx,
19289            )
19290        })
19291        .unwrap()
19292        .await;
19293    assert_eq!(
19294        editor.update(cx, |editor, cx| editor.text(cx)),
19295        buffer_text.to_string() + prettier_format_suffix,
19296        "Test prettier formatting was not applied to the original buffer text",
19297    );
19298
19299    update_test_language_settings(cx, |settings| {
19300        settings.defaults.formatter = Some(FormatterList::default())
19301    });
19302    let format = editor.update_in(cx, |editor, window, cx| {
19303        editor.perform_format(
19304            project.clone(),
19305            FormatTrigger::Manual,
19306            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19307            window,
19308            cx,
19309        )
19310    });
19311    format.await.unwrap();
19312    assert_eq!(
19313        editor.update(cx, |editor, cx| editor.text(cx)),
19314        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19315        "Autoformatting (via test prettier) was not applied to the original buffer text",
19316    );
19317}
19318
19319#[gpui::test]
19320async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19321    init_test(cx, |settings| {
19322        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19323    });
19324
19325    let fs = FakeFs::new(cx.executor());
19326    fs.insert_file(path!("/file.settings"), Default::default())
19327        .await;
19328
19329    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19330    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19331
19332    let ts_lang = Arc::new(Language::new(
19333        LanguageConfig {
19334            name: "TypeScript".into(),
19335            matcher: LanguageMatcher {
19336                path_suffixes: vec!["ts".to_string()],
19337                ..LanguageMatcher::default()
19338            },
19339            prettier_parser_name: Some("typescript".to_string()),
19340            ..LanguageConfig::default()
19341        },
19342        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19343    ));
19344
19345    language_registry.add(ts_lang.clone());
19346
19347    update_test_language_settings(cx, |settings| {
19348        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19349    });
19350
19351    let test_plugin = "test_plugin";
19352    let _ = language_registry.register_fake_lsp(
19353        "TypeScript",
19354        FakeLspAdapter {
19355            prettier_plugins: vec![test_plugin],
19356            ..Default::default()
19357        },
19358    );
19359
19360    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19361    let buffer = project
19362        .update(cx, |project, cx| {
19363            project.open_local_buffer(path!("/file.settings"), cx)
19364        })
19365        .await
19366        .unwrap();
19367
19368    project.update(cx, |project, cx| {
19369        project.set_language_for_buffer(&buffer, ts_lang, cx)
19370    });
19371
19372    let buffer_text = "one\ntwo\nthree\n";
19373    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19374    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19375    editor.update_in(cx, |editor, window, cx| {
19376        editor.set_text(buffer_text, window, cx)
19377    });
19378
19379    editor
19380        .update_in(cx, |editor, window, cx| {
19381            editor.perform_format(
19382                project.clone(),
19383                FormatTrigger::Manual,
19384                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19385                window,
19386                cx,
19387            )
19388        })
19389        .unwrap()
19390        .await;
19391    assert_eq!(
19392        editor.update(cx, |editor, cx| editor.text(cx)),
19393        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19394        "Test prettier formatting was not applied to the original buffer text",
19395    );
19396
19397    update_test_language_settings(cx, |settings| {
19398        settings.defaults.formatter = Some(FormatterList::default())
19399    });
19400    let format = editor.update_in(cx, |editor, window, cx| {
19401        editor.perform_format(
19402            project.clone(),
19403            FormatTrigger::Manual,
19404            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19405            window,
19406            cx,
19407        )
19408    });
19409    format.await.unwrap();
19410
19411    assert_eq!(
19412        editor.update(cx, |editor, cx| editor.text(cx)),
19413        buffer_text.to_string()
19414            + prettier_format_suffix
19415            + "\ntypescript\n"
19416            + prettier_format_suffix
19417            + "\ntypescript",
19418        "Autoformatting (via test prettier) was not applied to the original buffer text",
19419    );
19420}
19421
19422#[gpui::test]
19423async fn test_addition_reverts(cx: &mut TestAppContext) {
19424    init_test(cx, |_| {});
19425    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19426    let base_text = indoc! {r#"
19427        struct Row;
19428        struct Row1;
19429        struct Row2;
19430
19431        struct Row4;
19432        struct Row5;
19433        struct Row6;
19434
19435        struct Row8;
19436        struct Row9;
19437        struct Row10;"#};
19438
19439    // When addition hunks are not adjacent to carets, no hunk revert is performed
19440    assert_hunk_revert(
19441        indoc! {r#"struct Row;
19442                   struct Row1;
19443                   struct Row1.1;
19444                   struct Row1.2;
19445                   struct Row2;ˇ
19446
19447                   struct Row4;
19448                   struct Row5;
19449                   struct Row6;
19450
19451                   struct Row8;
19452                   ˇstruct Row9;
19453                   struct Row9.1;
19454                   struct Row9.2;
19455                   struct Row9.3;
19456                   struct Row10;"#},
19457        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19458        indoc! {r#"struct Row;
19459                   struct Row1;
19460                   struct Row1.1;
19461                   struct Row1.2;
19462                   struct Row2;ˇ
19463
19464                   struct Row4;
19465                   struct Row5;
19466                   struct Row6;
19467
19468                   struct Row8;
19469                   ˇstruct Row9;
19470                   struct Row9.1;
19471                   struct Row9.2;
19472                   struct Row9.3;
19473                   struct Row10;"#},
19474        base_text,
19475        &mut cx,
19476    );
19477    // Same for selections
19478    assert_hunk_revert(
19479        indoc! {r#"struct Row;
19480                   struct Row1;
19481                   struct Row2;
19482                   struct Row2.1;
19483                   struct Row2.2;
19484                   «ˇ
19485                   struct Row4;
19486                   struct» Row5;
19487                   «struct Row6;
19488                   ˇ»
19489                   struct Row9.1;
19490                   struct Row9.2;
19491                   struct Row9.3;
19492                   struct Row8;
19493                   struct Row9;
19494                   struct Row10;"#},
19495        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19496        indoc! {r#"struct Row;
19497                   struct Row1;
19498                   struct Row2;
19499                   struct Row2.1;
19500                   struct Row2.2;
19501                   «ˇ
19502                   struct Row4;
19503                   struct» Row5;
19504                   «struct Row6;
19505                   ˇ»
19506                   struct Row9.1;
19507                   struct Row9.2;
19508                   struct Row9.3;
19509                   struct Row8;
19510                   struct Row9;
19511                   struct Row10;"#},
19512        base_text,
19513        &mut cx,
19514    );
19515
19516    // When carets and selections intersect the addition hunks, those are reverted.
19517    // Adjacent carets got merged.
19518    assert_hunk_revert(
19519        indoc! {r#"struct Row;
19520                   ˇ// something on the top
19521                   struct Row1;
19522                   struct Row2;
19523                   struct Roˇw3.1;
19524                   struct Row2.2;
19525                   struct Row2.3;ˇ
19526
19527                   struct Row4;
19528                   struct ˇRow5.1;
19529                   struct Row5.2;
19530                   struct «Rowˇ»5.3;
19531                   struct Row5;
19532                   struct Row6;
19533                   ˇ
19534                   struct Row9.1;
19535                   struct «Rowˇ»9.2;
19536                   struct «ˇRow»9.3;
19537                   struct Row8;
19538                   struct Row9;
19539                   «ˇ// something on bottom»
19540                   struct Row10;"#},
19541        vec![
19542            DiffHunkStatusKind::Added,
19543            DiffHunkStatusKind::Added,
19544            DiffHunkStatusKind::Added,
19545            DiffHunkStatusKind::Added,
19546            DiffHunkStatusKind::Added,
19547        ],
19548        indoc! {r#"struct Row;
19549                   ˇstruct Row1;
19550                   struct Row2;
19551                   ˇ
19552                   struct Row4;
19553                   ˇstruct Row5;
19554                   struct Row6;
19555                   ˇ
19556                   ˇstruct Row8;
19557                   struct Row9;
19558                   ˇstruct Row10;"#},
19559        base_text,
19560        &mut cx,
19561    );
19562}
19563
19564#[gpui::test]
19565async fn test_modification_reverts(cx: &mut TestAppContext) {
19566    init_test(cx, |_| {});
19567    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19568    let base_text = indoc! {r#"
19569        struct Row;
19570        struct Row1;
19571        struct Row2;
19572
19573        struct Row4;
19574        struct Row5;
19575        struct Row6;
19576
19577        struct Row8;
19578        struct Row9;
19579        struct Row10;"#};
19580
19581    // Modification hunks behave the same as the addition ones.
19582    assert_hunk_revert(
19583        indoc! {r#"struct Row;
19584                   struct Row1;
19585                   struct Row33;
19586                   ˇ
19587                   struct Row4;
19588                   struct Row5;
19589                   struct Row6;
19590                   ˇ
19591                   struct Row99;
19592                   struct Row9;
19593                   struct Row10;"#},
19594        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19595        indoc! {r#"struct Row;
19596                   struct Row1;
19597                   struct Row33;
19598                   ˇ
19599                   struct Row4;
19600                   struct Row5;
19601                   struct Row6;
19602                   ˇ
19603                   struct Row99;
19604                   struct Row9;
19605                   struct Row10;"#},
19606        base_text,
19607        &mut cx,
19608    );
19609    assert_hunk_revert(
19610        indoc! {r#"struct Row;
19611                   struct Row1;
19612                   struct Row33;
19613                   «ˇ
19614                   struct Row4;
19615                   struct» Row5;
19616                   «struct Row6;
19617                   ˇ»
19618                   struct Row99;
19619                   struct Row9;
19620                   struct Row10;"#},
19621        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19622        indoc! {r#"struct Row;
19623                   struct Row1;
19624                   struct Row33;
19625                   «ˇ
19626                   struct Row4;
19627                   struct» Row5;
19628                   «struct Row6;
19629                   ˇ»
19630                   struct Row99;
19631                   struct Row9;
19632                   struct Row10;"#},
19633        base_text,
19634        &mut cx,
19635    );
19636
19637    assert_hunk_revert(
19638        indoc! {r#"ˇstruct Row1.1;
19639                   struct Row1;
19640                   «ˇstr»uct Row22;
19641
19642                   struct ˇRow44;
19643                   struct Row5;
19644                   struct «Rˇ»ow66;ˇ
19645
19646                   «struˇ»ct Row88;
19647                   struct Row9;
19648                   struct Row1011;ˇ"#},
19649        vec![
19650            DiffHunkStatusKind::Modified,
19651            DiffHunkStatusKind::Modified,
19652            DiffHunkStatusKind::Modified,
19653            DiffHunkStatusKind::Modified,
19654            DiffHunkStatusKind::Modified,
19655            DiffHunkStatusKind::Modified,
19656        ],
19657        indoc! {r#"struct Row;
19658                   ˇstruct Row1;
19659                   struct Row2;
19660                   ˇ
19661                   struct Row4;
19662                   ˇstruct Row5;
19663                   struct Row6;
19664                   ˇ
19665                   struct Row8;
19666                   ˇstruct Row9;
19667                   struct Row10;ˇ"#},
19668        base_text,
19669        &mut cx,
19670    );
19671}
19672
19673#[gpui::test]
19674async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19675    init_test(cx, |_| {});
19676    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19677    let base_text = indoc! {r#"
19678        one
19679
19680        two
19681        three
19682        "#};
19683
19684    cx.set_head_text(base_text);
19685    cx.set_state("\nˇ\n");
19686    cx.executor().run_until_parked();
19687    cx.update_editor(|editor, _window, cx| {
19688        editor.expand_selected_diff_hunks(cx);
19689    });
19690    cx.executor().run_until_parked();
19691    cx.update_editor(|editor, window, cx| {
19692        editor.backspace(&Default::default(), window, cx);
19693    });
19694    cx.run_until_parked();
19695    cx.assert_state_with_diff(
19696        indoc! {r#"
19697
19698        - two
19699        - threeˇ
19700        +
19701        "#}
19702        .to_string(),
19703    );
19704}
19705
19706#[gpui::test]
19707async fn test_deletion_reverts(cx: &mut TestAppContext) {
19708    init_test(cx, |_| {});
19709    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19710    let base_text = indoc! {r#"struct Row;
19711struct Row1;
19712struct Row2;
19713
19714struct Row4;
19715struct Row5;
19716struct Row6;
19717
19718struct Row8;
19719struct Row9;
19720struct Row10;"#};
19721
19722    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19723    assert_hunk_revert(
19724        indoc! {r#"struct Row;
19725                   struct Row2;
19726
19727                   ˇstruct Row4;
19728                   struct Row5;
19729                   struct Row6;
19730                   ˇ
19731                   struct Row8;
19732                   struct Row10;"#},
19733        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19734        indoc! {r#"struct Row;
19735                   struct Row2;
19736
19737                   ˇstruct Row4;
19738                   struct Row5;
19739                   struct Row6;
19740                   ˇ
19741                   struct Row8;
19742                   struct Row10;"#},
19743        base_text,
19744        &mut cx,
19745    );
19746    assert_hunk_revert(
19747        indoc! {r#"struct Row;
19748                   struct Row2;
19749
19750                   «ˇstruct Row4;
19751                   struct» Row5;
19752                   «struct Row6;
19753                   ˇ»
19754                   struct Row8;
19755                   struct Row10;"#},
19756        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19757        indoc! {r#"struct Row;
19758                   struct Row2;
19759
19760                   «ˇstruct Row4;
19761                   struct» Row5;
19762                   «struct Row6;
19763                   ˇ»
19764                   struct Row8;
19765                   struct Row10;"#},
19766        base_text,
19767        &mut cx,
19768    );
19769
19770    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19771    assert_hunk_revert(
19772        indoc! {r#"struct Row;
19773                   ˇstruct Row2;
19774
19775                   struct Row4;
19776                   struct Row5;
19777                   struct Row6;
19778
19779                   struct Row8;ˇ
19780                   struct Row10;"#},
19781        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19782        indoc! {r#"struct Row;
19783                   struct Row1;
19784                   ˇstruct Row2;
19785
19786                   struct Row4;
19787                   struct Row5;
19788                   struct Row6;
19789
19790                   struct Row8;ˇ
19791                   struct Row9;
19792                   struct Row10;"#},
19793        base_text,
19794        &mut cx,
19795    );
19796    assert_hunk_revert(
19797        indoc! {r#"struct Row;
19798                   struct Row2«ˇ;
19799                   struct Row4;
19800                   struct» Row5;
19801                   «struct Row6;
19802
19803                   struct Row8;ˇ»
19804                   struct Row10;"#},
19805        vec![
19806            DiffHunkStatusKind::Deleted,
19807            DiffHunkStatusKind::Deleted,
19808            DiffHunkStatusKind::Deleted,
19809        ],
19810        indoc! {r#"struct Row;
19811                   struct Row1;
19812                   struct Row2«ˇ;
19813
19814                   struct Row4;
19815                   struct» Row5;
19816                   «struct Row6;
19817
19818                   struct Row8;ˇ»
19819                   struct Row9;
19820                   struct Row10;"#},
19821        base_text,
19822        &mut cx,
19823    );
19824}
19825
19826#[gpui::test]
19827async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19828    init_test(cx, |_| {});
19829
19830    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19831    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19832    let base_text_3 =
19833        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19834
19835    let text_1 = edit_first_char_of_every_line(base_text_1);
19836    let text_2 = edit_first_char_of_every_line(base_text_2);
19837    let text_3 = edit_first_char_of_every_line(base_text_3);
19838
19839    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19840    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19841    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19842
19843    let multibuffer = cx.new(|cx| {
19844        let mut multibuffer = MultiBuffer::new(ReadWrite);
19845        multibuffer.push_excerpts(
19846            buffer_1.clone(),
19847            [
19848                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19849                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19850                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19851            ],
19852            cx,
19853        );
19854        multibuffer.push_excerpts(
19855            buffer_2.clone(),
19856            [
19857                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19858                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19859                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19860            ],
19861            cx,
19862        );
19863        multibuffer.push_excerpts(
19864            buffer_3.clone(),
19865            [
19866                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19867                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19868                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19869            ],
19870            cx,
19871        );
19872        multibuffer
19873    });
19874
19875    let fs = FakeFs::new(cx.executor());
19876    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19877    let (editor, cx) = cx
19878        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19879    editor.update_in(cx, |editor, _window, cx| {
19880        for (buffer, diff_base) in [
19881            (buffer_1.clone(), base_text_1),
19882            (buffer_2.clone(), base_text_2),
19883            (buffer_3.clone(), base_text_3),
19884        ] {
19885            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19886            editor
19887                .buffer
19888                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19889        }
19890    });
19891    cx.executor().run_until_parked();
19892
19893    editor.update_in(cx, |editor, window, cx| {
19894        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}");
19895        editor.select_all(&SelectAll, window, cx);
19896        editor.git_restore(&Default::default(), window, cx);
19897    });
19898    cx.executor().run_until_parked();
19899
19900    // When all ranges are selected, all buffer hunks are reverted.
19901    editor.update(cx, |editor, cx| {
19902        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");
19903    });
19904    buffer_1.update(cx, |buffer, _| {
19905        assert_eq!(buffer.text(), base_text_1);
19906    });
19907    buffer_2.update(cx, |buffer, _| {
19908        assert_eq!(buffer.text(), base_text_2);
19909    });
19910    buffer_3.update(cx, |buffer, _| {
19911        assert_eq!(buffer.text(), base_text_3);
19912    });
19913
19914    editor.update_in(cx, |editor, window, cx| {
19915        editor.undo(&Default::default(), window, cx);
19916    });
19917
19918    editor.update_in(cx, |editor, window, cx| {
19919        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19920            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19921        });
19922        editor.git_restore(&Default::default(), window, cx);
19923    });
19924
19925    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19926    // but not affect buffer_2 and its related excerpts.
19927    editor.update(cx, |editor, cx| {
19928        assert_eq!(
19929            editor.text(cx),
19930            "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}"
19931        );
19932    });
19933    buffer_1.update(cx, |buffer, _| {
19934        assert_eq!(buffer.text(), base_text_1);
19935    });
19936    buffer_2.update(cx, |buffer, _| {
19937        assert_eq!(
19938            buffer.text(),
19939            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19940        );
19941    });
19942    buffer_3.update(cx, |buffer, _| {
19943        assert_eq!(
19944            buffer.text(),
19945            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19946        );
19947    });
19948
19949    fn edit_first_char_of_every_line(text: &str) -> String {
19950        text.split('\n')
19951            .map(|line| format!("X{}", &line[1..]))
19952            .collect::<Vec<_>>()
19953            .join("\n")
19954    }
19955}
19956
19957#[gpui::test]
19958async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19959    init_test(cx, |_| {});
19960
19961    let cols = 4;
19962    let rows = 10;
19963    let sample_text_1 = sample_text(rows, cols, 'a');
19964    assert_eq!(
19965        sample_text_1,
19966        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19967    );
19968    let sample_text_2 = sample_text(rows, cols, 'l');
19969    assert_eq!(
19970        sample_text_2,
19971        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19972    );
19973    let sample_text_3 = sample_text(rows, cols, 'v');
19974    assert_eq!(
19975        sample_text_3,
19976        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19977    );
19978
19979    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19980    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19981    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19982
19983    let multi_buffer = cx.new(|cx| {
19984        let mut multibuffer = MultiBuffer::new(ReadWrite);
19985        multibuffer.push_excerpts(
19986            buffer_1.clone(),
19987            [
19988                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19989                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19990                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19991            ],
19992            cx,
19993        );
19994        multibuffer.push_excerpts(
19995            buffer_2.clone(),
19996            [
19997                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19998                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19999                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20000            ],
20001            cx,
20002        );
20003        multibuffer.push_excerpts(
20004            buffer_3.clone(),
20005            [
20006                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20007                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20008                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
20009            ],
20010            cx,
20011        );
20012        multibuffer
20013    });
20014
20015    let fs = FakeFs::new(cx.executor());
20016    fs.insert_tree(
20017        "/a",
20018        json!({
20019            "main.rs": sample_text_1,
20020            "other.rs": sample_text_2,
20021            "lib.rs": sample_text_3,
20022        }),
20023    )
20024    .await;
20025    let project = Project::test(fs, ["/a".as_ref()], cx).await;
20026    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
20027    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
20028    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
20029        Editor::new(
20030            EditorMode::full(),
20031            multi_buffer,
20032            Some(project.clone()),
20033            window,
20034            cx,
20035        )
20036    });
20037    let multibuffer_item_id = workspace
20038        .update(cx, |workspace, window, cx| {
20039            assert!(
20040                workspace.active_item(cx).is_none(),
20041                "active item should be None before the first item is added"
20042            );
20043            workspace.add_item_to_active_pane(
20044                Box::new(multi_buffer_editor.clone()),
20045                None,
20046                true,
20047                window,
20048                cx,
20049            );
20050            let active_item = workspace
20051                .active_item(cx)
20052                .expect("should have an active item after adding the multi buffer");
20053            assert_eq!(
20054                active_item.buffer_kind(cx),
20055                ItemBufferKind::Multibuffer,
20056                "A multi buffer was expected to active after adding"
20057            );
20058            active_item.item_id()
20059        })
20060        .unwrap();
20061    cx.executor().run_until_parked();
20062
20063    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20064        editor.change_selections(
20065            SelectionEffects::scroll(Autoscroll::Next),
20066            window,
20067            cx,
20068            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
20069        );
20070        editor.open_excerpts(&OpenExcerpts, window, cx);
20071    });
20072    cx.executor().run_until_parked();
20073    let first_item_id = workspace
20074        .update(cx, |workspace, window, cx| {
20075            let active_item = workspace
20076                .active_item(cx)
20077                .expect("should have an active item after navigating into the 1st buffer");
20078            let first_item_id = active_item.item_id();
20079            assert_ne!(
20080                first_item_id, multibuffer_item_id,
20081                "Should navigate into the 1st buffer and activate it"
20082            );
20083            assert_eq!(
20084                active_item.buffer_kind(cx),
20085                ItemBufferKind::Singleton,
20086                "New active item should be a singleton buffer"
20087            );
20088            assert_eq!(
20089                active_item
20090                    .act_as::<Editor>(cx)
20091                    .expect("should have navigated into an editor for the 1st buffer")
20092                    .read(cx)
20093                    .text(cx),
20094                sample_text_1
20095            );
20096
20097            workspace
20098                .go_back(workspace.active_pane().downgrade(), window, cx)
20099                .detach_and_log_err(cx);
20100
20101            first_item_id
20102        })
20103        .unwrap();
20104    cx.executor().run_until_parked();
20105    workspace
20106        .update(cx, |workspace, _, cx| {
20107            let active_item = workspace
20108                .active_item(cx)
20109                .expect("should have an active item after navigating back");
20110            assert_eq!(
20111                active_item.item_id(),
20112                multibuffer_item_id,
20113                "Should navigate back to the multi buffer"
20114            );
20115            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20116        })
20117        .unwrap();
20118
20119    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20120        editor.change_selections(
20121            SelectionEffects::scroll(Autoscroll::Next),
20122            window,
20123            cx,
20124            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20125        );
20126        editor.open_excerpts(&OpenExcerpts, window, cx);
20127    });
20128    cx.executor().run_until_parked();
20129    let second_item_id = workspace
20130        .update(cx, |workspace, window, cx| {
20131            let active_item = workspace
20132                .active_item(cx)
20133                .expect("should have an active item after navigating into the 2nd buffer");
20134            let second_item_id = active_item.item_id();
20135            assert_ne!(
20136                second_item_id, multibuffer_item_id,
20137                "Should navigate away from the multibuffer"
20138            );
20139            assert_ne!(
20140                second_item_id, first_item_id,
20141                "Should navigate into the 2nd buffer and activate it"
20142            );
20143            assert_eq!(
20144                active_item.buffer_kind(cx),
20145                ItemBufferKind::Singleton,
20146                "New active item should be a singleton buffer"
20147            );
20148            assert_eq!(
20149                active_item
20150                    .act_as::<Editor>(cx)
20151                    .expect("should have navigated into an editor")
20152                    .read(cx)
20153                    .text(cx),
20154                sample_text_2
20155            );
20156
20157            workspace
20158                .go_back(workspace.active_pane().downgrade(), window, cx)
20159                .detach_and_log_err(cx);
20160
20161            second_item_id
20162        })
20163        .unwrap();
20164    cx.executor().run_until_parked();
20165    workspace
20166        .update(cx, |workspace, _, cx| {
20167            let active_item = workspace
20168                .active_item(cx)
20169                .expect("should have an active item after navigating back from the 2nd buffer");
20170            assert_eq!(
20171                active_item.item_id(),
20172                multibuffer_item_id,
20173                "Should navigate back from the 2nd buffer to the multi buffer"
20174            );
20175            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20176        })
20177        .unwrap();
20178
20179    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20180        editor.change_selections(
20181            SelectionEffects::scroll(Autoscroll::Next),
20182            window,
20183            cx,
20184            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20185        );
20186        editor.open_excerpts(&OpenExcerpts, window, cx);
20187    });
20188    cx.executor().run_until_parked();
20189    workspace
20190        .update(cx, |workspace, window, cx| {
20191            let active_item = workspace
20192                .active_item(cx)
20193                .expect("should have an active item after navigating into the 3rd buffer");
20194            let third_item_id = active_item.item_id();
20195            assert_ne!(
20196                third_item_id, multibuffer_item_id,
20197                "Should navigate into the 3rd buffer and activate it"
20198            );
20199            assert_ne!(third_item_id, first_item_id);
20200            assert_ne!(third_item_id, second_item_id);
20201            assert_eq!(
20202                active_item.buffer_kind(cx),
20203                ItemBufferKind::Singleton,
20204                "New active item should be a singleton buffer"
20205            );
20206            assert_eq!(
20207                active_item
20208                    .act_as::<Editor>(cx)
20209                    .expect("should have navigated into an editor")
20210                    .read(cx)
20211                    .text(cx),
20212                sample_text_3
20213            );
20214
20215            workspace
20216                .go_back(workspace.active_pane().downgrade(), window, cx)
20217                .detach_and_log_err(cx);
20218        })
20219        .unwrap();
20220    cx.executor().run_until_parked();
20221    workspace
20222        .update(cx, |workspace, _, cx| {
20223            let active_item = workspace
20224                .active_item(cx)
20225                .expect("should have an active item after navigating back from the 3rd buffer");
20226            assert_eq!(
20227                active_item.item_id(),
20228                multibuffer_item_id,
20229                "Should navigate back from the 3rd buffer to the multi buffer"
20230            );
20231            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20232        })
20233        .unwrap();
20234}
20235
20236#[gpui::test]
20237async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20238    init_test(cx, |_| {});
20239
20240    let mut cx = EditorTestContext::new(cx).await;
20241
20242    let diff_base = r#"
20243        use some::mod;
20244
20245        const A: u32 = 42;
20246
20247        fn main() {
20248            println!("hello");
20249
20250            println!("world");
20251        }
20252        "#
20253    .unindent();
20254
20255    cx.set_state(
20256        &r#"
20257        use some::modified;
20258
20259        ˇ
20260        fn main() {
20261            println!("hello there");
20262
20263            println!("around the");
20264            println!("world");
20265        }
20266        "#
20267        .unindent(),
20268    );
20269
20270    cx.set_head_text(&diff_base);
20271    executor.run_until_parked();
20272
20273    cx.update_editor(|editor, window, cx| {
20274        editor.go_to_next_hunk(&GoToHunk, window, cx);
20275        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20276    });
20277    executor.run_until_parked();
20278    cx.assert_state_with_diff(
20279        r#"
20280          use some::modified;
20281
20282
20283          fn main() {
20284        -     println!("hello");
20285        + ˇ    println!("hello there");
20286
20287              println!("around the");
20288              println!("world");
20289          }
20290        "#
20291        .unindent(),
20292    );
20293
20294    cx.update_editor(|editor, window, cx| {
20295        for _ in 0..2 {
20296            editor.go_to_next_hunk(&GoToHunk, window, cx);
20297            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20298        }
20299    });
20300    executor.run_until_parked();
20301    cx.assert_state_with_diff(
20302        r#"
20303        - use some::mod;
20304        + ˇuse some::modified;
20305
20306
20307          fn main() {
20308        -     println!("hello");
20309        +     println!("hello there");
20310
20311        +     println!("around the");
20312              println!("world");
20313          }
20314        "#
20315        .unindent(),
20316    );
20317
20318    cx.update_editor(|editor, window, cx| {
20319        editor.go_to_next_hunk(&GoToHunk, window, cx);
20320        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20321    });
20322    executor.run_until_parked();
20323    cx.assert_state_with_diff(
20324        r#"
20325        - use some::mod;
20326        + use some::modified;
20327
20328        - const A: u32 = 42;
20329          ˇ
20330          fn main() {
20331        -     println!("hello");
20332        +     println!("hello there");
20333
20334        +     println!("around the");
20335              println!("world");
20336          }
20337        "#
20338        .unindent(),
20339    );
20340
20341    cx.update_editor(|editor, window, cx| {
20342        editor.cancel(&Cancel, window, cx);
20343    });
20344
20345    cx.assert_state_with_diff(
20346        r#"
20347          use some::modified;
20348
20349          ˇ
20350          fn main() {
20351              println!("hello there");
20352
20353              println!("around the");
20354              println!("world");
20355          }
20356        "#
20357        .unindent(),
20358    );
20359}
20360
20361#[gpui::test]
20362async fn test_diff_base_change_with_expanded_diff_hunks(
20363    executor: BackgroundExecutor,
20364    cx: &mut TestAppContext,
20365) {
20366    init_test(cx, |_| {});
20367
20368    let mut cx = EditorTestContext::new(cx).await;
20369
20370    let diff_base = r#"
20371        use some::mod1;
20372        use some::mod2;
20373
20374        const A: u32 = 42;
20375        const B: u32 = 42;
20376        const C: u32 = 42;
20377
20378        fn main() {
20379            println!("hello");
20380
20381            println!("world");
20382        }
20383        "#
20384    .unindent();
20385
20386    cx.set_state(
20387        &r#"
20388        use some::mod2;
20389
20390        const A: u32 = 42;
20391        const C: u32 = 42;
20392
20393        fn main(ˇ) {
20394            //println!("hello");
20395
20396            println!("world");
20397            //
20398            //
20399        }
20400        "#
20401        .unindent(),
20402    );
20403
20404    cx.set_head_text(&diff_base);
20405    executor.run_until_parked();
20406
20407    cx.update_editor(|editor, window, cx| {
20408        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20409    });
20410    executor.run_until_parked();
20411    cx.assert_state_with_diff(
20412        r#"
20413        - use some::mod1;
20414          use some::mod2;
20415
20416          const A: u32 = 42;
20417        - const B: u32 = 42;
20418          const C: u32 = 42;
20419
20420          fn main(ˇ) {
20421        -     println!("hello");
20422        +     //println!("hello");
20423
20424              println!("world");
20425        +     //
20426        +     //
20427          }
20428        "#
20429        .unindent(),
20430    );
20431
20432    cx.set_head_text("new diff base!");
20433    executor.run_until_parked();
20434    cx.assert_state_with_diff(
20435        r#"
20436        - new diff base!
20437        + use some::mod2;
20438        +
20439        + const A: u32 = 42;
20440        + const C: u32 = 42;
20441        +
20442        + fn main(ˇ) {
20443        +     //println!("hello");
20444        +
20445        +     println!("world");
20446        +     //
20447        +     //
20448        + }
20449        "#
20450        .unindent(),
20451    );
20452}
20453
20454#[gpui::test]
20455async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20456    init_test(cx, |_| {});
20457
20458    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20459    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20460    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20461    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20462    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20463    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20464
20465    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20466    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20467    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20468
20469    let multi_buffer = cx.new(|cx| {
20470        let mut multibuffer = MultiBuffer::new(ReadWrite);
20471        multibuffer.push_excerpts(
20472            buffer_1.clone(),
20473            [
20474                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20475                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20476                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20477            ],
20478            cx,
20479        );
20480        multibuffer.push_excerpts(
20481            buffer_2.clone(),
20482            [
20483                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20484                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20485                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20486            ],
20487            cx,
20488        );
20489        multibuffer.push_excerpts(
20490            buffer_3.clone(),
20491            [
20492                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20493                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20494                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20495            ],
20496            cx,
20497        );
20498        multibuffer
20499    });
20500
20501    let editor =
20502        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20503    editor
20504        .update(cx, |editor, _window, cx| {
20505            for (buffer, diff_base) in [
20506                (buffer_1.clone(), file_1_old),
20507                (buffer_2.clone(), file_2_old),
20508                (buffer_3.clone(), file_3_old),
20509            ] {
20510                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20511                editor
20512                    .buffer
20513                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20514            }
20515        })
20516        .unwrap();
20517
20518    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20519    cx.run_until_parked();
20520
20521    cx.assert_editor_state(
20522        &"
20523            ˇaaa
20524            ccc
20525            ddd
20526
20527            ggg
20528            hhh
20529
20530
20531            lll
20532            mmm
20533            NNN
20534
20535            qqq
20536            rrr
20537
20538            uuu
20539            111
20540            222
20541            333
20542
20543            666
20544            777
20545
20546            000
20547            !!!"
20548        .unindent(),
20549    );
20550
20551    cx.update_editor(|editor, window, cx| {
20552        editor.select_all(&SelectAll, window, cx);
20553        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20554    });
20555    cx.executor().run_until_parked();
20556
20557    cx.assert_state_with_diff(
20558        "
20559            «aaa
20560          - bbb
20561            ccc
20562            ddd
20563
20564            ggg
20565            hhh
20566
20567
20568            lll
20569            mmm
20570          - nnn
20571          + NNN
20572
20573            qqq
20574            rrr
20575
20576            uuu
20577            111
20578            222
20579            333
20580
20581          + 666
20582            777
20583
20584            000
20585            !!!ˇ»"
20586            .unindent(),
20587    );
20588}
20589
20590#[gpui::test]
20591async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20592    init_test(cx, |_| {});
20593
20594    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20595    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20596
20597    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20598    let multi_buffer = cx.new(|cx| {
20599        let mut multibuffer = MultiBuffer::new(ReadWrite);
20600        multibuffer.push_excerpts(
20601            buffer.clone(),
20602            [
20603                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20604                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20605                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20606            ],
20607            cx,
20608        );
20609        multibuffer
20610    });
20611
20612    let editor =
20613        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20614    editor
20615        .update(cx, |editor, _window, cx| {
20616            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20617            editor
20618                .buffer
20619                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20620        })
20621        .unwrap();
20622
20623    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20624    cx.run_until_parked();
20625
20626    cx.update_editor(|editor, window, cx| {
20627        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20628    });
20629    cx.executor().run_until_parked();
20630
20631    // When the start of a hunk coincides with the start of its excerpt,
20632    // the hunk is expanded. When the start of a hunk is earlier than
20633    // the start of its excerpt, the hunk is not expanded.
20634    cx.assert_state_with_diff(
20635        "
20636            ˇaaa
20637          - bbb
20638          + BBB
20639
20640          - ddd
20641          - eee
20642          + DDD
20643          + EEE
20644            fff
20645
20646            iii
20647        "
20648        .unindent(),
20649    );
20650}
20651
20652#[gpui::test]
20653async fn test_edits_around_expanded_insertion_hunks(
20654    executor: BackgroundExecutor,
20655    cx: &mut TestAppContext,
20656) {
20657    init_test(cx, |_| {});
20658
20659    let mut cx = EditorTestContext::new(cx).await;
20660
20661    let diff_base = r#"
20662        use some::mod1;
20663        use some::mod2;
20664
20665        const A: u32 = 42;
20666
20667        fn main() {
20668            println!("hello");
20669
20670            println!("world");
20671        }
20672        "#
20673    .unindent();
20674    executor.run_until_parked();
20675    cx.set_state(
20676        &r#"
20677        use some::mod1;
20678        use some::mod2;
20679
20680        const A: u32 = 42;
20681        const B: u32 = 42;
20682        const C: u32 = 42;
20683        ˇ
20684
20685        fn main() {
20686            println!("hello");
20687
20688            println!("world");
20689        }
20690        "#
20691        .unindent(),
20692    );
20693
20694    cx.set_head_text(&diff_base);
20695    executor.run_until_parked();
20696
20697    cx.update_editor(|editor, window, cx| {
20698        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20699    });
20700    executor.run_until_parked();
20701
20702    cx.assert_state_with_diff(
20703        r#"
20704        use some::mod1;
20705        use some::mod2;
20706
20707        const A: u32 = 42;
20708      + const B: u32 = 42;
20709      + const C: u32 = 42;
20710      + ˇ
20711
20712        fn main() {
20713            println!("hello");
20714
20715            println!("world");
20716        }
20717      "#
20718        .unindent(),
20719    );
20720
20721    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20722    executor.run_until_parked();
20723
20724    cx.assert_state_with_diff(
20725        r#"
20726        use some::mod1;
20727        use some::mod2;
20728
20729        const A: u32 = 42;
20730      + const B: u32 = 42;
20731      + const C: u32 = 42;
20732      + const D: u32 = 42;
20733      + ˇ
20734
20735        fn main() {
20736            println!("hello");
20737
20738            println!("world");
20739        }
20740      "#
20741        .unindent(),
20742    );
20743
20744    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20745    executor.run_until_parked();
20746
20747    cx.assert_state_with_diff(
20748        r#"
20749        use some::mod1;
20750        use some::mod2;
20751
20752        const A: u32 = 42;
20753      + const B: u32 = 42;
20754      + const C: u32 = 42;
20755      + const D: u32 = 42;
20756      + const E: u32 = 42;
20757      + ˇ
20758
20759        fn main() {
20760            println!("hello");
20761
20762            println!("world");
20763        }
20764      "#
20765        .unindent(),
20766    );
20767
20768    cx.update_editor(|editor, window, cx| {
20769        editor.delete_line(&DeleteLine, window, cx);
20770    });
20771    executor.run_until_parked();
20772
20773    cx.assert_state_with_diff(
20774        r#"
20775        use some::mod1;
20776        use some::mod2;
20777
20778        const A: u32 = 42;
20779      + const B: u32 = 42;
20780      + const C: u32 = 42;
20781      + const D: u32 = 42;
20782      + const E: u32 = 42;
20783        ˇ
20784        fn main() {
20785            println!("hello");
20786
20787            println!("world");
20788        }
20789      "#
20790        .unindent(),
20791    );
20792
20793    cx.update_editor(|editor, window, cx| {
20794        editor.move_up(&MoveUp, window, cx);
20795        editor.delete_line(&DeleteLine, window, cx);
20796        editor.move_up(&MoveUp, window, cx);
20797        editor.delete_line(&DeleteLine, window, cx);
20798        editor.move_up(&MoveUp, window, cx);
20799        editor.delete_line(&DeleteLine, window, cx);
20800    });
20801    executor.run_until_parked();
20802    cx.assert_state_with_diff(
20803        r#"
20804        use some::mod1;
20805        use some::mod2;
20806
20807        const A: u32 = 42;
20808      + const B: u32 = 42;
20809        ˇ
20810        fn main() {
20811            println!("hello");
20812
20813            println!("world");
20814        }
20815      "#
20816        .unindent(),
20817    );
20818
20819    cx.update_editor(|editor, window, cx| {
20820        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20821        editor.delete_line(&DeleteLine, window, cx);
20822    });
20823    executor.run_until_parked();
20824    cx.assert_state_with_diff(
20825        r#"
20826        ˇ
20827        fn main() {
20828            println!("hello");
20829
20830            println!("world");
20831        }
20832      "#
20833        .unindent(),
20834    );
20835}
20836
20837#[gpui::test]
20838async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20839    init_test(cx, |_| {});
20840
20841    let mut cx = EditorTestContext::new(cx).await;
20842    cx.set_head_text(indoc! { "
20843        one
20844        two
20845        three
20846        four
20847        five
20848        "
20849    });
20850    cx.set_state(indoc! { "
20851        one
20852        ˇthree
20853        five
20854    "});
20855    cx.run_until_parked();
20856    cx.update_editor(|editor, window, cx| {
20857        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20858    });
20859    cx.assert_state_with_diff(
20860        indoc! { "
20861        one
20862      - two
20863        ˇthree
20864      - four
20865        five
20866    "}
20867        .to_string(),
20868    );
20869    cx.update_editor(|editor, window, cx| {
20870        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20871    });
20872
20873    cx.assert_state_with_diff(
20874        indoc! { "
20875        one
20876        ˇthree
20877        five
20878    "}
20879        .to_string(),
20880    );
20881
20882    cx.set_state(indoc! { "
20883        one
20884        ˇTWO
20885        three
20886        four
20887        five
20888    "});
20889    cx.run_until_parked();
20890    cx.update_editor(|editor, window, cx| {
20891        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20892    });
20893
20894    cx.assert_state_with_diff(
20895        indoc! { "
20896            one
20897          - two
20898          + ˇTWO
20899            three
20900            four
20901            five
20902        "}
20903        .to_string(),
20904    );
20905    cx.update_editor(|editor, window, cx| {
20906        editor.move_up(&Default::default(), window, cx);
20907        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20908    });
20909    cx.assert_state_with_diff(
20910        indoc! { "
20911            one
20912            ˇTWO
20913            three
20914            four
20915            five
20916        "}
20917        .to_string(),
20918    );
20919}
20920
20921#[gpui::test]
20922async fn test_edits_around_expanded_deletion_hunks(
20923    executor: BackgroundExecutor,
20924    cx: &mut TestAppContext,
20925) {
20926    init_test(cx, |_| {});
20927
20928    let mut cx = EditorTestContext::new(cx).await;
20929
20930    let diff_base = r#"
20931        use some::mod1;
20932        use some::mod2;
20933
20934        const A: u32 = 42;
20935        const B: u32 = 42;
20936        const C: u32 = 42;
20937
20938
20939        fn main() {
20940            println!("hello");
20941
20942            println!("world");
20943        }
20944    "#
20945    .unindent();
20946    executor.run_until_parked();
20947    cx.set_state(
20948        &r#"
20949        use some::mod1;
20950        use some::mod2;
20951
20952        ˇconst B: u32 = 42;
20953        const C: u32 = 42;
20954
20955
20956        fn main() {
20957            println!("hello");
20958
20959            println!("world");
20960        }
20961        "#
20962        .unindent(),
20963    );
20964
20965    cx.set_head_text(&diff_base);
20966    executor.run_until_parked();
20967
20968    cx.update_editor(|editor, window, cx| {
20969        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20970    });
20971    executor.run_until_parked();
20972
20973    cx.assert_state_with_diff(
20974        r#"
20975        use some::mod1;
20976        use some::mod2;
20977
20978      - const A: u32 = 42;
20979        ˇconst B: u32 = 42;
20980        const C: u32 = 42;
20981
20982
20983        fn main() {
20984            println!("hello");
20985
20986            println!("world");
20987        }
20988      "#
20989        .unindent(),
20990    );
20991
20992    cx.update_editor(|editor, window, cx| {
20993        editor.delete_line(&DeleteLine, window, cx);
20994    });
20995    executor.run_until_parked();
20996    cx.assert_state_with_diff(
20997        r#"
20998        use some::mod1;
20999        use some::mod2;
21000
21001      - const A: u32 = 42;
21002      - const B: u32 = 42;
21003        ˇconst C: u32 = 42;
21004
21005
21006        fn main() {
21007            println!("hello");
21008
21009            println!("world");
21010        }
21011      "#
21012        .unindent(),
21013    );
21014
21015    cx.update_editor(|editor, window, cx| {
21016        editor.delete_line(&DeleteLine, window, cx);
21017    });
21018    executor.run_until_parked();
21019    cx.assert_state_with_diff(
21020        r#"
21021        use some::mod1;
21022        use some::mod2;
21023
21024      - const A: u32 = 42;
21025      - const B: u32 = 42;
21026      - const C: u32 = 42;
21027        ˇ
21028
21029        fn main() {
21030            println!("hello");
21031
21032            println!("world");
21033        }
21034      "#
21035        .unindent(),
21036    );
21037
21038    cx.update_editor(|editor, window, cx| {
21039        editor.handle_input("replacement", window, cx);
21040    });
21041    executor.run_until_parked();
21042    cx.assert_state_with_diff(
21043        r#"
21044        use some::mod1;
21045        use some::mod2;
21046
21047      - const A: u32 = 42;
21048      - const B: u32 = 42;
21049      - const C: u32 = 42;
21050      -
21051      + replacementˇ
21052
21053        fn main() {
21054            println!("hello");
21055
21056            println!("world");
21057        }
21058      "#
21059        .unindent(),
21060    );
21061}
21062
21063#[gpui::test]
21064async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21065    init_test(cx, |_| {});
21066
21067    let mut cx = EditorTestContext::new(cx).await;
21068
21069    let base_text = r#"
21070        one
21071        two
21072        three
21073        four
21074        five
21075    "#
21076    .unindent();
21077    executor.run_until_parked();
21078    cx.set_state(
21079        &r#"
21080        one
21081        two
21082        fˇour
21083        five
21084        "#
21085        .unindent(),
21086    );
21087
21088    cx.set_head_text(&base_text);
21089    executor.run_until_parked();
21090
21091    cx.update_editor(|editor, window, cx| {
21092        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21093    });
21094    executor.run_until_parked();
21095
21096    cx.assert_state_with_diff(
21097        r#"
21098          one
21099          two
21100        - three
21101          fˇour
21102          five
21103        "#
21104        .unindent(),
21105    );
21106
21107    cx.update_editor(|editor, window, cx| {
21108        editor.backspace(&Backspace, window, cx);
21109        editor.backspace(&Backspace, window, cx);
21110    });
21111    executor.run_until_parked();
21112    cx.assert_state_with_diff(
21113        r#"
21114          one
21115          two
21116        - threeˇ
21117        - four
21118        + our
21119          five
21120        "#
21121        .unindent(),
21122    );
21123}
21124
21125#[gpui::test]
21126async fn test_edit_after_expanded_modification_hunk(
21127    executor: BackgroundExecutor,
21128    cx: &mut TestAppContext,
21129) {
21130    init_test(cx, |_| {});
21131
21132    let mut cx = EditorTestContext::new(cx).await;
21133
21134    let diff_base = r#"
21135        use some::mod1;
21136        use some::mod2;
21137
21138        const A: u32 = 42;
21139        const B: u32 = 42;
21140        const C: u32 = 42;
21141        const D: u32 = 42;
21142
21143
21144        fn main() {
21145            println!("hello");
21146
21147            println!("world");
21148        }"#
21149    .unindent();
21150
21151    cx.set_state(
21152        &r#"
21153        use some::mod1;
21154        use some::mod2;
21155
21156        const A: u32 = 42;
21157        const B: u32 = 42;
21158        const C: u32 = 43ˇ
21159        const D: u32 = 42;
21160
21161
21162        fn main() {
21163            println!("hello");
21164
21165            println!("world");
21166        }"#
21167        .unindent(),
21168    );
21169
21170    cx.set_head_text(&diff_base);
21171    executor.run_until_parked();
21172    cx.update_editor(|editor, window, cx| {
21173        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21174    });
21175    executor.run_until_parked();
21176
21177    cx.assert_state_with_diff(
21178        r#"
21179        use some::mod1;
21180        use some::mod2;
21181
21182        const A: u32 = 42;
21183        const B: u32 = 42;
21184      - const C: u32 = 42;
21185      + const C: u32 = 43ˇ
21186        const D: u32 = 42;
21187
21188
21189        fn main() {
21190            println!("hello");
21191
21192            println!("world");
21193        }"#
21194        .unindent(),
21195    );
21196
21197    cx.update_editor(|editor, window, cx| {
21198        editor.handle_input("\nnew_line\n", window, cx);
21199    });
21200    executor.run_until_parked();
21201
21202    cx.assert_state_with_diff(
21203        r#"
21204        use some::mod1;
21205        use some::mod2;
21206
21207        const A: u32 = 42;
21208        const B: u32 = 42;
21209      - const C: u32 = 42;
21210      + const C: u32 = 43
21211      + new_line
21212      + ˇ
21213        const D: u32 = 42;
21214
21215
21216        fn main() {
21217            println!("hello");
21218
21219            println!("world");
21220        }"#
21221        .unindent(),
21222    );
21223}
21224
21225#[gpui::test]
21226async fn test_stage_and_unstage_added_file_hunk(
21227    executor: BackgroundExecutor,
21228    cx: &mut TestAppContext,
21229) {
21230    init_test(cx, |_| {});
21231
21232    let mut cx = EditorTestContext::new(cx).await;
21233    cx.update_editor(|editor, _, cx| {
21234        editor.set_expand_all_diff_hunks(cx);
21235    });
21236
21237    let working_copy = r#"
21238            ˇfn main() {
21239                println!("hello, world!");
21240            }
21241        "#
21242    .unindent();
21243
21244    cx.set_state(&working_copy);
21245    executor.run_until_parked();
21246
21247    cx.assert_state_with_diff(
21248        r#"
21249            + ˇfn main() {
21250            +     println!("hello, world!");
21251            + }
21252        "#
21253        .unindent(),
21254    );
21255    cx.assert_index_text(None);
21256
21257    cx.update_editor(|editor, window, cx| {
21258        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21259    });
21260    executor.run_until_parked();
21261    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21262    cx.assert_state_with_diff(
21263        r#"
21264            + ˇfn main() {
21265            +     println!("hello, world!");
21266            + }
21267        "#
21268        .unindent(),
21269    );
21270
21271    cx.update_editor(|editor, window, cx| {
21272        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21273    });
21274    executor.run_until_parked();
21275    cx.assert_index_text(None);
21276}
21277
21278async fn setup_indent_guides_editor(
21279    text: &str,
21280    cx: &mut TestAppContext,
21281) -> (BufferId, EditorTestContext) {
21282    init_test(cx, |_| {});
21283
21284    let mut cx = EditorTestContext::new(cx).await;
21285
21286    let buffer_id = cx.update_editor(|editor, window, cx| {
21287        editor.set_text(text, window, cx);
21288        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21289
21290        buffer_ids[0]
21291    });
21292
21293    (buffer_id, cx)
21294}
21295
21296fn assert_indent_guides(
21297    range: Range<u32>,
21298    expected: Vec<IndentGuide>,
21299    active_indices: Option<Vec<usize>>,
21300    cx: &mut EditorTestContext,
21301) {
21302    let indent_guides = cx.update_editor(|editor, window, cx| {
21303        let snapshot = editor.snapshot(window, cx).display_snapshot;
21304        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21305            editor,
21306            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21307            true,
21308            &snapshot,
21309            cx,
21310        );
21311
21312        indent_guides.sort_by(|a, b| {
21313            a.depth.cmp(&b.depth).then(
21314                a.start_row
21315                    .cmp(&b.start_row)
21316                    .then(a.end_row.cmp(&b.end_row)),
21317            )
21318        });
21319        indent_guides
21320    });
21321
21322    if let Some(expected) = active_indices {
21323        let active_indices = cx.update_editor(|editor, window, cx| {
21324            let snapshot = editor.snapshot(window, cx).display_snapshot;
21325            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21326        });
21327
21328        assert_eq!(
21329            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21330            expected,
21331            "Active indent guide indices do not match"
21332        );
21333    }
21334
21335    assert_eq!(indent_guides, expected, "Indent guides do not match");
21336}
21337
21338fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21339    IndentGuide {
21340        buffer_id,
21341        start_row: MultiBufferRow(start_row),
21342        end_row: MultiBufferRow(end_row),
21343        depth,
21344        tab_size: 4,
21345        settings: IndentGuideSettings {
21346            enabled: true,
21347            line_width: 1,
21348            active_line_width: 1,
21349            coloring: IndentGuideColoring::default(),
21350            background_coloring: IndentGuideBackgroundColoring::default(),
21351        },
21352    }
21353}
21354
21355#[gpui::test]
21356async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21357    let (buffer_id, mut cx) = setup_indent_guides_editor(
21358        &"
21359        fn main() {
21360            let a = 1;
21361        }"
21362        .unindent(),
21363        cx,
21364    )
21365    .await;
21366
21367    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21368}
21369
21370#[gpui::test]
21371async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21372    let (buffer_id, mut cx) = setup_indent_guides_editor(
21373        &"
21374        fn main() {
21375            let a = 1;
21376            let b = 2;
21377        }"
21378        .unindent(),
21379        cx,
21380    )
21381    .await;
21382
21383    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21384}
21385
21386#[gpui::test]
21387async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21388    let (buffer_id, mut cx) = setup_indent_guides_editor(
21389        &"
21390        fn main() {
21391            let a = 1;
21392            if a == 3 {
21393                let b = 2;
21394            } else {
21395                let c = 3;
21396            }
21397        }"
21398        .unindent(),
21399        cx,
21400    )
21401    .await;
21402
21403    assert_indent_guides(
21404        0..8,
21405        vec![
21406            indent_guide(buffer_id, 1, 6, 0),
21407            indent_guide(buffer_id, 3, 3, 1),
21408            indent_guide(buffer_id, 5, 5, 1),
21409        ],
21410        None,
21411        &mut cx,
21412    );
21413}
21414
21415#[gpui::test]
21416async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21417    let (buffer_id, mut cx) = setup_indent_guides_editor(
21418        &"
21419        fn main() {
21420            let a = 1;
21421                let b = 2;
21422            let c = 3;
21423        }"
21424        .unindent(),
21425        cx,
21426    )
21427    .await;
21428
21429    assert_indent_guides(
21430        0..5,
21431        vec![
21432            indent_guide(buffer_id, 1, 3, 0),
21433            indent_guide(buffer_id, 2, 2, 1),
21434        ],
21435        None,
21436        &mut cx,
21437    );
21438}
21439
21440#[gpui::test]
21441async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21442    let (buffer_id, mut cx) = setup_indent_guides_editor(
21443        &"
21444        fn main() {
21445            let a = 1;
21446
21447            let c = 3;
21448        }"
21449        .unindent(),
21450        cx,
21451    )
21452    .await;
21453
21454    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21455}
21456
21457#[gpui::test]
21458async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21459    let (buffer_id, mut cx) = setup_indent_guides_editor(
21460        &"
21461        fn main() {
21462            let a = 1;
21463
21464            let c = 3;
21465
21466            if a == 3 {
21467                let b = 2;
21468            } else {
21469                let c = 3;
21470            }
21471        }"
21472        .unindent(),
21473        cx,
21474    )
21475    .await;
21476
21477    assert_indent_guides(
21478        0..11,
21479        vec![
21480            indent_guide(buffer_id, 1, 9, 0),
21481            indent_guide(buffer_id, 6, 6, 1),
21482            indent_guide(buffer_id, 8, 8, 1),
21483        ],
21484        None,
21485        &mut cx,
21486    );
21487}
21488
21489#[gpui::test]
21490async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21491    let (buffer_id, mut cx) = setup_indent_guides_editor(
21492        &"
21493        fn main() {
21494            let a = 1;
21495
21496            let c = 3;
21497
21498            if a == 3 {
21499                let b = 2;
21500            } else {
21501                let c = 3;
21502            }
21503        }"
21504        .unindent(),
21505        cx,
21506    )
21507    .await;
21508
21509    assert_indent_guides(
21510        1..11,
21511        vec![
21512            indent_guide(buffer_id, 1, 9, 0),
21513            indent_guide(buffer_id, 6, 6, 1),
21514            indent_guide(buffer_id, 8, 8, 1),
21515        ],
21516        None,
21517        &mut cx,
21518    );
21519}
21520
21521#[gpui::test]
21522async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21523    let (buffer_id, mut cx) = setup_indent_guides_editor(
21524        &"
21525        fn main() {
21526            let a = 1;
21527
21528            let c = 3;
21529
21530            if a == 3 {
21531                let b = 2;
21532            } else {
21533                let c = 3;
21534            }
21535        }"
21536        .unindent(),
21537        cx,
21538    )
21539    .await;
21540
21541    assert_indent_guides(
21542        1..10,
21543        vec![
21544            indent_guide(buffer_id, 1, 9, 0),
21545            indent_guide(buffer_id, 6, 6, 1),
21546            indent_guide(buffer_id, 8, 8, 1),
21547        ],
21548        None,
21549        &mut cx,
21550    );
21551}
21552
21553#[gpui::test]
21554async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21555    let (buffer_id, mut cx) = setup_indent_guides_editor(
21556        &"
21557        fn main() {
21558            if a {
21559                b(
21560                    c,
21561                    d,
21562                )
21563            } else {
21564                e(
21565                    f
21566                )
21567            }
21568        }"
21569        .unindent(),
21570        cx,
21571    )
21572    .await;
21573
21574    assert_indent_guides(
21575        0..11,
21576        vec![
21577            indent_guide(buffer_id, 1, 10, 0),
21578            indent_guide(buffer_id, 2, 5, 1),
21579            indent_guide(buffer_id, 7, 9, 1),
21580            indent_guide(buffer_id, 3, 4, 2),
21581            indent_guide(buffer_id, 8, 8, 2),
21582        ],
21583        None,
21584        &mut cx,
21585    );
21586
21587    cx.update_editor(|editor, window, cx| {
21588        editor.fold_at(MultiBufferRow(2), window, cx);
21589        assert_eq!(
21590            editor.display_text(cx),
21591            "
21592            fn main() {
21593                if a {
21594                    b(⋯
21595                    )
21596                } else {
21597                    e(
21598                        f
21599                    )
21600                }
21601            }"
21602            .unindent()
21603        );
21604    });
21605
21606    assert_indent_guides(
21607        0..11,
21608        vec![
21609            indent_guide(buffer_id, 1, 10, 0),
21610            indent_guide(buffer_id, 2, 5, 1),
21611            indent_guide(buffer_id, 7, 9, 1),
21612            indent_guide(buffer_id, 8, 8, 2),
21613        ],
21614        None,
21615        &mut cx,
21616    );
21617}
21618
21619#[gpui::test]
21620async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21621    let (buffer_id, mut cx) = setup_indent_guides_editor(
21622        &"
21623        block1
21624            block2
21625                block3
21626                    block4
21627            block2
21628        block1
21629        block1"
21630            .unindent(),
21631        cx,
21632    )
21633    .await;
21634
21635    assert_indent_guides(
21636        1..10,
21637        vec![
21638            indent_guide(buffer_id, 1, 4, 0),
21639            indent_guide(buffer_id, 2, 3, 1),
21640            indent_guide(buffer_id, 3, 3, 2),
21641        ],
21642        None,
21643        &mut cx,
21644    );
21645}
21646
21647#[gpui::test]
21648async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21649    let (buffer_id, mut cx) = setup_indent_guides_editor(
21650        &"
21651        block1
21652            block2
21653                block3
21654
21655        block1
21656        block1"
21657            .unindent(),
21658        cx,
21659    )
21660    .await;
21661
21662    assert_indent_guides(
21663        0..6,
21664        vec![
21665            indent_guide(buffer_id, 1, 2, 0),
21666            indent_guide(buffer_id, 2, 2, 1),
21667        ],
21668        None,
21669        &mut cx,
21670    );
21671}
21672
21673#[gpui::test]
21674async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21675    let (buffer_id, mut cx) = setup_indent_guides_editor(
21676        &"
21677        function component() {
21678        \treturn (
21679        \t\t\t
21680        \t\t<div>
21681        \t\t\t<abc></abc>
21682        \t\t</div>
21683        \t)
21684        }"
21685        .unindent(),
21686        cx,
21687    )
21688    .await;
21689
21690    assert_indent_guides(
21691        0..8,
21692        vec![
21693            indent_guide(buffer_id, 1, 6, 0),
21694            indent_guide(buffer_id, 2, 5, 1),
21695            indent_guide(buffer_id, 4, 4, 2),
21696        ],
21697        None,
21698        &mut cx,
21699    );
21700}
21701
21702#[gpui::test]
21703async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21704    let (buffer_id, mut cx) = setup_indent_guides_editor(
21705        &"
21706        function component() {
21707        \treturn (
21708        \t
21709        \t\t<div>
21710        \t\t\t<abc></abc>
21711        \t\t</div>
21712        \t)
21713        }"
21714        .unindent(),
21715        cx,
21716    )
21717    .await;
21718
21719    assert_indent_guides(
21720        0..8,
21721        vec![
21722            indent_guide(buffer_id, 1, 6, 0),
21723            indent_guide(buffer_id, 2, 5, 1),
21724            indent_guide(buffer_id, 4, 4, 2),
21725        ],
21726        None,
21727        &mut cx,
21728    );
21729}
21730
21731#[gpui::test]
21732async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21733    let (buffer_id, mut cx) = setup_indent_guides_editor(
21734        &"
21735        block1
21736
21737
21738
21739            block2
21740        "
21741        .unindent(),
21742        cx,
21743    )
21744    .await;
21745
21746    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21747}
21748
21749#[gpui::test]
21750async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21751    let (buffer_id, mut cx) = setup_indent_guides_editor(
21752        &"
21753        def a:
21754        \tb = 3
21755        \tif True:
21756        \t\tc = 4
21757        \t\td = 5
21758        \tprint(b)
21759        "
21760        .unindent(),
21761        cx,
21762    )
21763    .await;
21764
21765    assert_indent_guides(
21766        0..6,
21767        vec![
21768            indent_guide(buffer_id, 1, 5, 0),
21769            indent_guide(buffer_id, 3, 4, 1),
21770        ],
21771        None,
21772        &mut cx,
21773    );
21774}
21775
21776#[gpui::test]
21777async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21778    let (buffer_id, mut cx) = setup_indent_guides_editor(
21779        &"
21780    fn main() {
21781        let a = 1;
21782    }"
21783        .unindent(),
21784        cx,
21785    )
21786    .await;
21787
21788    cx.update_editor(|editor, window, cx| {
21789        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21790            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21791        });
21792    });
21793
21794    assert_indent_guides(
21795        0..3,
21796        vec![indent_guide(buffer_id, 1, 1, 0)],
21797        Some(vec![0]),
21798        &mut cx,
21799    );
21800}
21801
21802#[gpui::test]
21803async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21804    let (buffer_id, mut cx) = setup_indent_guides_editor(
21805        &"
21806    fn main() {
21807        if 1 == 2 {
21808            let a = 1;
21809        }
21810    }"
21811        .unindent(),
21812        cx,
21813    )
21814    .await;
21815
21816    cx.update_editor(|editor, window, cx| {
21817        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21818            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21819        });
21820    });
21821
21822    assert_indent_guides(
21823        0..4,
21824        vec![
21825            indent_guide(buffer_id, 1, 3, 0),
21826            indent_guide(buffer_id, 2, 2, 1),
21827        ],
21828        Some(vec![1]),
21829        &mut cx,
21830    );
21831
21832    cx.update_editor(|editor, window, cx| {
21833        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21834            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21835        });
21836    });
21837
21838    assert_indent_guides(
21839        0..4,
21840        vec![
21841            indent_guide(buffer_id, 1, 3, 0),
21842            indent_guide(buffer_id, 2, 2, 1),
21843        ],
21844        Some(vec![1]),
21845        &mut cx,
21846    );
21847
21848    cx.update_editor(|editor, window, cx| {
21849        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21850            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21851        });
21852    });
21853
21854    assert_indent_guides(
21855        0..4,
21856        vec![
21857            indent_guide(buffer_id, 1, 3, 0),
21858            indent_guide(buffer_id, 2, 2, 1),
21859        ],
21860        Some(vec![0]),
21861        &mut cx,
21862    );
21863}
21864
21865#[gpui::test]
21866async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21867    let (buffer_id, mut cx) = setup_indent_guides_editor(
21868        &"
21869    fn main() {
21870        let a = 1;
21871
21872        let b = 2;
21873    }"
21874        .unindent(),
21875        cx,
21876    )
21877    .await;
21878
21879    cx.update_editor(|editor, window, cx| {
21880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21881            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21882        });
21883    });
21884
21885    assert_indent_guides(
21886        0..5,
21887        vec![indent_guide(buffer_id, 1, 3, 0)],
21888        Some(vec![0]),
21889        &mut cx,
21890    );
21891}
21892
21893#[gpui::test]
21894async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21895    let (buffer_id, mut cx) = setup_indent_guides_editor(
21896        &"
21897    def m:
21898        a = 1
21899        pass"
21900            .unindent(),
21901        cx,
21902    )
21903    .await;
21904
21905    cx.update_editor(|editor, window, cx| {
21906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21907            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21908        });
21909    });
21910
21911    assert_indent_guides(
21912        0..3,
21913        vec![indent_guide(buffer_id, 1, 2, 0)],
21914        Some(vec![0]),
21915        &mut cx,
21916    );
21917}
21918
21919#[gpui::test]
21920async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21921    init_test(cx, |_| {});
21922    let mut cx = EditorTestContext::new(cx).await;
21923    let text = indoc! {
21924        "
21925        impl A {
21926            fn b() {
21927                0;
21928                3;
21929                5;
21930                6;
21931                7;
21932            }
21933        }
21934        "
21935    };
21936    let base_text = indoc! {
21937        "
21938        impl A {
21939            fn b() {
21940                0;
21941                1;
21942                2;
21943                3;
21944                4;
21945            }
21946            fn c() {
21947                5;
21948                6;
21949                7;
21950            }
21951        }
21952        "
21953    };
21954
21955    cx.update_editor(|editor, window, cx| {
21956        editor.set_text(text, window, cx);
21957
21958        editor.buffer().update(cx, |multibuffer, cx| {
21959            let buffer = multibuffer.as_singleton().unwrap();
21960            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21961
21962            multibuffer.set_all_diff_hunks_expanded(cx);
21963            multibuffer.add_diff(diff, cx);
21964
21965            buffer.read(cx).remote_id()
21966        })
21967    });
21968    cx.run_until_parked();
21969
21970    cx.assert_state_with_diff(
21971        indoc! { "
21972          impl A {
21973              fn b() {
21974                  0;
21975        -         1;
21976        -         2;
21977                  3;
21978        -         4;
21979        -     }
21980        -     fn c() {
21981                  5;
21982                  6;
21983                  7;
21984              }
21985          }
21986          ˇ"
21987        }
21988        .to_string(),
21989    );
21990
21991    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21992        editor
21993            .snapshot(window, cx)
21994            .buffer_snapshot()
21995            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21996            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21997            .collect::<Vec<_>>()
21998    });
21999    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
22000    assert_eq!(
22001        actual_guides,
22002        vec![
22003            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
22004            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
22005            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
22006        ]
22007    );
22008}
22009
22010#[gpui::test]
22011async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
22012    init_test(cx, |_| {});
22013    let mut cx = EditorTestContext::new(cx).await;
22014
22015    let diff_base = r#"
22016        a
22017        b
22018        c
22019        "#
22020    .unindent();
22021
22022    cx.set_state(
22023        &r#"
22024        ˇA
22025        b
22026        C
22027        "#
22028        .unindent(),
22029    );
22030    cx.set_head_text(&diff_base);
22031    cx.update_editor(|editor, window, cx| {
22032        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22033    });
22034    executor.run_until_parked();
22035
22036    let both_hunks_expanded = r#"
22037        - a
22038        + ˇA
22039          b
22040        - c
22041        + C
22042        "#
22043    .unindent();
22044
22045    cx.assert_state_with_diff(both_hunks_expanded.clone());
22046
22047    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22048        let snapshot = editor.snapshot(window, cx);
22049        let hunks = editor
22050            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22051            .collect::<Vec<_>>();
22052        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22053        hunks
22054            .into_iter()
22055            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22056            .collect::<Vec<_>>()
22057    });
22058    assert_eq!(hunk_ranges.len(), 2);
22059
22060    cx.update_editor(|editor, _, cx| {
22061        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22062    });
22063    executor.run_until_parked();
22064
22065    let second_hunk_expanded = r#"
22066          ˇA
22067          b
22068        - c
22069        + C
22070        "#
22071    .unindent();
22072
22073    cx.assert_state_with_diff(second_hunk_expanded);
22074
22075    cx.update_editor(|editor, _, cx| {
22076        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22077    });
22078    executor.run_until_parked();
22079
22080    cx.assert_state_with_diff(both_hunks_expanded.clone());
22081
22082    cx.update_editor(|editor, _, cx| {
22083        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22084    });
22085    executor.run_until_parked();
22086
22087    let first_hunk_expanded = r#"
22088        - a
22089        + ˇA
22090          b
22091          C
22092        "#
22093    .unindent();
22094
22095    cx.assert_state_with_diff(first_hunk_expanded);
22096
22097    cx.update_editor(|editor, _, cx| {
22098        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22099    });
22100    executor.run_until_parked();
22101
22102    cx.assert_state_with_diff(both_hunks_expanded);
22103
22104    cx.set_state(
22105        &r#"
22106        ˇA
22107        b
22108        "#
22109        .unindent(),
22110    );
22111    cx.run_until_parked();
22112
22113    // TODO this cursor position seems bad
22114    cx.assert_state_with_diff(
22115        r#"
22116        - ˇa
22117        + A
22118          b
22119        "#
22120        .unindent(),
22121    );
22122
22123    cx.update_editor(|editor, window, cx| {
22124        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22125    });
22126
22127    cx.assert_state_with_diff(
22128        r#"
22129            - ˇa
22130            + A
22131              b
22132            - c
22133            "#
22134        .unindent(),
22135    );
22136
22137    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22138        let snapshot = editor.snapshot(window, cx);
22139        let hunks = editor
22140            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22141            .collect::<Vec<_>>();
22142        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22143        hunks
22144            .into_iter()
22145            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22146            .collect::<Vec<_>>()
22147    });
22148    assert_eq!(hunk_ranges.len(), 2);
22149
22150    cx.update_editor(|editor, _, cx| {
22151        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22152    });
22153    executor.run_until_parked();
22154
22155    cx.assert_state_with_diff(
22156        r#"
22157        - ˇa
22158        + A
22159          b
22160        "#
22161        .unindent(),
22162    );
22163}
22164
22165#[gpui::test]
22166async fn test_toggle_deletion_hunk_at_start_of_file(
22167    executor: BackgroundExecutor,
22168    cx: &mut TestAppContext,
22169) {
22170    init_test(cx, |_| {});
22171    let mut cx = EditorTestContext::new(cx).await;
22172
22173    let diff_base = r#"
22174        a
22175        b
22176        c
22177        "#
22178    .unindent();
22179
22180    cx.set_state(
22181        &r#"
22182        ˇb
22183        c
22184        "#
22185        .unindent(),
22186    );
22187    cx.set_head_text(&diff_base);
22188    cx.update_editor(|editor, window, cx| {
22189        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22190    });
22191    executor.run_until_parked();
22192
22193    let hunk_expanded = r#"
22194        - a
22195          ˇb
22196          c
22197        "#
22198    .unindent();
22199
22200    cx.assert_state_with_diff(hunk_expanded.clone());
22201
22202    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22203        let snapshot = editor.snapshot(window, cx);
22204        let hunks = editor
22205            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22206            .collect::<Vec<_>>();
22207        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22208        hunks
22209            .into_iter()
22210            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22211            .collect::<Vec<_>>()
22212    });
22213    assert_eq!(hunk_ranges.len(), 1);
22214
22215    cx.update_editor(|editor, _, cx| {
22216        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22217    });
22218    executor.run_until_parked();
22219
22220    let hunk_collapsed = r#"
22221          ˇb
22222          c
22223        "#
22224    .unindent();
22225
22226    cx.assert_state_with_diff(hunk_collapsed);
22227
22228    cx.update_editor(|editor, _, cx| {
22229        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22230    });
22231    executor.run_until_parked();
22232
22233    cx.assert_state_with_diff(hunk_expanded);
22234}
22235
22236#[gpui::test]
22237async fn test_expand_first_line_diff_hunk_keeps_deleted_lines_visible(
22238    executor: BackgroundExecutor,
22239    cx: &mut TestAppContext,
22240) {
22241    init_test(cx, |_| {});
22242    let mut cx = EditorTestContext::new(cx).await;
22243
22244    cx.set_state("ˇnew\nsecond\nthird\n");
22245    cx.set_head_text("old\nsecond\nthird\n");
22246    cx.update_editor(|editor, window, cx| {
22247        editor.scroll(gpui::Point { x: 0., y: 0. }, None, window, cx);
22248    });
22249    executor.run_until_parked();
22250    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22251
22252    // Expanding a diff hunk at the first line inserts deleted lines above the first buffer line.
22253    cx.update_editor(|editor, window, cx| {
22254        let snapshot = editor.snapshot(window, cx);
22255        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22256        let hunks = editor
22257            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22258            .collect::<Vec<_>>();
22259        assert_eq!(hunks.len(), 1);
22260        let hunk_range = Anchor::range_in_buffer(excerpt_id, hunks[0].buffer_range.clone());
22261        editor.toggle_single_diff_hunk(hunk_range, cx)
22262    });
22263    executor.run_until_parked();
22264    cx.assert_state_with_diff("- old\n+ ˇnew\n  second\n  third\n".to_string());
22265
22266    // Keep the editor scrolled to the top so the full hunk remains visible.
22267    assert_eq!(cx.update_editor(|e, _, cx| e.scroll_position(cx)).y, 0.0);
22268}
22269
22270#[gpui::test]
22271async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22272    init_test(cx, |_| {});
22273
22274    let fs = FakeFs::new(cx.executor());
22275    fs.insert_tree(
22276        path!("/test"),
22277        json!({
22278            ".git": {},
22279            "file-1": "ONE\n",
22280            "file-2": "TWO\n",
22281            "file-3": "THREE\n",
22282        }),
22283    )
22284    .await;
22285
22286    fs.set_head_for_repo(
22287        path!("/test/.git").as_ref(),
22288        &[
22289            ("file-1", "one\n".into()),
22290            ("file-2", "two\n".into()),
22291            ("file-3", "three\n".into()),
22292        ],
22293        "deadbeef",
22294    );
22295
22296    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22297    let mut buffers = vec![];
22298    for i in 1..=3 {
22299        let buffer = project
22300            .update(cx, |project, cx| {
22301                let path = format!(path!("/test/file-{}"), i);
22302                project.open_local_buffer(path, cx)
22303            })
22304            .await
22305            .unwrap();
22306        buffers.push(buffer);
22307    }
22308
22309    let multibuffer = cx.new(|cx| {
22310        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22311        multibuffer.set_all_diff_hunks_expanded(cx);
22312        for buffer in &buffers {
22313            let snapshot = buffer.read(cx).snapshot();
22314            multibuffer.set_excerpts_for_path(
22315                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22316                buffer.clone(),
22317                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22318                2,
22319                cx,
22320            );
22321        }
22322        multibuffer
22323    });
22324
22325    let editor = cx.add_window(|window, cx| {
22326        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22327    });
22328    cx.run_until_parked();
22329
22330    let snapshot = editor
22331        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22332        .unwrap();
22333    let hunks = snapshot
22334        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22335        .map(|hunk| match hunk {
22336            DisplayDiffHunk::Unfolded {
22337                display_row_range, ..
22338            } => display_row_range,
22339            DisplayDiffHunk::Folded { .. } => unreachable!(),
22340        })
22341        .collect::<Vec<_>>();
22342    assert_eq!(
22343        hunks,
22344        [
22345            DisplayRow(2)..DisplayRow(4),
22346            DisplayRow(7)..DisplayRow(9),
22347            DisplayRow(12)..DisplayRow(14),
22348        ]
22349    );
22350}
22351
22352#[gpui::test]
22353async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22354    init_test(cx, |_| {});
22355
22356    let mut cx = EditorTestContext::new(cx).await;
22357    cx.set_head_text(indoc! { "
22358        one
22359        two
22360        three
22361        four
22362        five
22363        "
22364    });
22365    cx.set_index_text(indoc! { "
22366        one
22367        two
22368        three
22369        four
22370        five
22371        "
22372    });
22373    cx.set_state(indoc! {"
22374        one
22375        TWO
22376        ˇTHREE
22377        FOUR
22378        five
22379    "});
22380    cx.run_until_parked();
22381    cx.update_editor(|editor, window, cx| {
22382        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22383    });
22384    cx.run_until_parked();
22385    cx.assert_index_text(Some(indoc! {"
22386        one
22387        TWO
22388        THREE
22389        FOUR
22390        five
22391    "}));
22392    cx.set_state(indoc! { "
22393        one
22394        TWO
22395        ˇTHREE-HUNDRED
22396        FOUR
22397        five
22398    "});
22399    cx.run_until_parked();
22400    cx.update_editor(|editor, window, cx| {
22401        let snapshot = editor.snapshot(window, cx);
22402        let hunks = editor
22403            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22404            .collect::<Vec<_>>();
22405        assert_eq!(hunks.len(), 1);
22406        assert_eq!(
22407            hunks[0].status(),
22408            DiffHunkStatus {
22409                kind: DiffHunkStatusKind::Modified,
22410                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22411            }
22412        );
22413
22414        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22415    });
22416    cx.run_until_parked();
22417    cx.assert_index_text(Some(indoc! {"
22418        one
22419        TWO
22420        THREE-HUNDRED
22421        FOUR
22422        five
22423    "}));
22424}
22425
22426#[gpui::test]
22427fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22428    init_test(cx, |_| {});
22429
22430    let editor = cx.add_window(|window, cx| {
22431        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22432        build_editor(buffer, window, cx)
22433    });
22434
22435    let render_args = Arc::new(Mutex::new(None));
22436    let snapshot = editor
22437        .update(cx, |editor, window, cx| {
22438            let snapshot = editor.buffer().read(cx).snapshot(cx);
22439            let range =
22440                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22441
22442            struct RenderArgs {
22443                row: MultiBufferRow,
22444                folded: bool,
22445                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22446            }
22447
22448            let crease = Crease::inline(
22449                range,
22450                FoldPlaceholder::test(),
22451                {
22452                    let toggle_callback = render_args.clone();
22453                    move |row, folded, callback, _window, _cx| {
22454                        *toggle_callback.lock() = Some(RenderArgs {
22455                            row,
22456                            folded,
22457                            callback,
22458                        });
22459                        div()
22460                    }
22461                },
22462                |_row, _folded, _window, _cx| div(),
22463            );
22464
22465            editor.insert_creases(Some(crease), cx);
22466            let snapshot = editor.snapshot(window, cx);
22467            let _div =
22468                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22469            snapshot
22470        })
22471        .unwrap();
22472
22473    let render_args = render_args.lock().take().unwrap();
22474    assert_eq!(render_args.row, MultiBufferRow(1));
22475    assert!(!render_args.folded);
22476    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22477
22478    cx.update_window(*editor, |_, window, cx| {
22479        (render_args.callback)(true, window, cx)
22480    })
22481    .unwrap();
22482    let snapshot = editor
22483        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22484        .unwrap();
22485    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22486
22487    cx.update_window(*editor, |_, window, cx| {
22488        (render_args.callback)(false, window, cx)
22489    })
22490    .unwrap();
22491    let snapshot = editor
22492        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22493        .unwrap();
22494    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22495}
22496
22497#[gpui::test]
22498async fn test_input_text(cx: &mut TestAppContext) {
22499    init_test(cx, |_| {});
22500    let mut cx = EditorTestContext::new(cx).await;
22501
22502    cx.set_state(
22503        &r#"ˇone
22504        two
22505
22506        three
22507        fourˇ
22508        five
22509
22510        siˇx"#
22511            .unindent(),
22512    );
22513
22514    cx.dispatch_action(HandleInput(String::new()));
22515    cx.assert_editor_state(
22516        &r#"ˇone
22517        two
22518
22519        three
22520        fourˇ
22521        five
22522
22523        siˇx"#
22524            .unindent(),
22525    );
22526
22527    cx.dispatch_action(HandleInput("AAAA".to_string()));
22528    cx.assert_editor_state(
22529        &r#"AAAAˇone
22530        two
22531
22532        three
22533        fourAAAAˇ
22534        five
22535
22536        siAAAAˇx"#
22537            .unindent(),
22538    );
22539}
22540
22541#[gpui::test]
22542async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22543    init_test(cx, |_| {});
22544
22545    let mut cx = EditorTestContext::new(cx).await;
22546    cx.set_state(
22547        r#"let foo = 1;
22548let foo = 2;
22549let foo = 3;
22550let fooˇ = 4;
22551let foo = 5;
22552let foo = 6;
22553let foo = 7;
22554let foo = 8;
22555let foo = 9;
22556let foo = 10;
22557let foo = 11;
22558let foo = 12;
22559let foo = 13;
22560let foo = 14;
22561let foo = 15;"#,
22562    );
22563
22564    cx.update_editor(|e, window, cx| {
22565        assert_eq!(
22566            e.next_scroll_position,
22567            NextScrollCursorCenterTopBottom::Center,
22568            "Default next scroll direction is center",
22569        );
22570
22571        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22572        assert_eq!(
22573            e.next_scroll_position,
22574            NextScrollCursorCenterTopBottom::Top,
22575            "After center, next scroll direction should be top",
22576        );
22577
22578        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22579        assert_eq!(
22580            e.next_scroll_position,
22581            NextScrollCursorCenterTopBottom::Bottom,
22582            "After top, next scroll direction should be bottom",
22583        );
22584
22585        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22586        assert_eq!(
22587            e.next_scroll_position,
22588            NextScrollCursorCenterTopBottom::Center,
22589            "After bottom, scrolling should start over",
22590        );
22591
22592        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22593        assert_eq!(
22594            e.next_scroll_position,
22595            NextScrollCursorCenterTopBottom::Top,
22596            "Scrolling continues if retriggered fast enough"
22597        );
22598    });
22599
22600    cx.executor()
22601        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22602    cx.executor().run_until_parked();
22603    cx.update_editor(|e, _, _| {
22604        assert_eq!(
22605            e.next_scroll_position,
22606            NextScrollCursorCenterTopBottom::Center,
22607            "If scrolling is not triggered fast enough, it should reset"
22608        );
22609    });
22610}
22611
22612#[gpui::test]
22613async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22614    init_test(cx, |_| {});
22615    let mut cx = EditorLspTestContext::new_rust(
22616        lsp::ServerCapabilities {
22617            definition_provider: Some(lsp::OneOf::Left(true)),
22618            references_provider: Some(lsp::OneOf::Left(true)),
22619            ..lsp::ServerCapabilities::default()
22620        },
22621        cx,
22622    )
22623    .await;
22624
22625    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22626        let go_to_definition = cx
22627            .lsp
22628            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22629                move |params, _| async move {
22630                    if empty_go_to_definition {
22631                        Ok(None)
22632                    } else {
22633                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22634                            uri: params.text_document_position_params.text_document.uri,
22635                            range: lsp::Range::new(
22636                                lsp::Position::new(4, 3),
22637                                lsp::Position::new(4, 6),
22638                            ),
22639                        })))
22640                    }
22641                },
22642            );
22643        let references = cx
22644            .lsp
22645            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22646                Ok(Some(vec![lsp::Location {
22647                    uri: params.text_document_position.text_document.uri,
22648                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22649                }]))
22650            });
22651        (go_to_definition, references)
22652    };
22653
22654    cx.set_state(
22655        &r#"fn one() {
22656            let mut a = ˇtwo();
22657        }
22658
22659        fn two() {}"#
22660            .unindent(),
22661    );
22662    set_up_lsp_handlers(false, &mut cx);
22663    let navigated = cx
22664        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22665        .await
22666        .expect("Failed to navigate to definition");
22667    assert_eq!(
22668        navigated,
22669        Navigated::Yes,
22670        "Should have navigated to definition from the GetDefinition response"
22671    );
22672    cx.assert_editor_state(
22673        &r#"fn one() {
22674            let mut a = two();
22675        }
22676
22677        fn «twoˇ»() {}"#
22678            .unindent(),
22679    );
22680
22681    let editors = cx.update_workspace(|workspace, _, cx| {
22682        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22683    });
22684    cx.update_editor(|_, _, test_editor_cx| {
22685        assert_eq!(
22686            editors.len(),
22687            1,
22688            "Initially, only one, test, editor should be open in the workspace"
22689        );
22690        assert_eq!(
22691            test_editor_cx.entity(),
22692            editors.last().expect("Asserted len is 1").clone()
22693        );
22694    });
22695
22696    set_up_lsp_handlers(true, &mut cx);
22697    let navigated = cx
22698        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22699        .await
22700        .expect("Failed to navigate to lookup references");
22701    assert_eq!(
22702        navigated,
22703        Navigated::Yes,
22704        "Should have navigated to references as a fallback after empty GoToDefinition response"
22705    );
22706    // We should not change the selections in the existing file,
22707    // if opening another milti buffer with the references
22708    cx.assert_editor_state(
22709        &r#"fn one() {
22710            let mut a = two();
22711        }
22712
22713        fn «twoˇ»() {}"#
22714            .unindent(),
22715    );
22716    let editors = cx.update_workspace(|workspace, _, cx| {
22717        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22718    });
22719    cx.update_editor(|_, _, test_editor_cx| {
22720        assert_eq!(
22721            editors.len(),
22722            2,
22723            "After falling back to references search, we open a new editor with the results"
22724        );
22725        let references_fallback_text = editors
22726            .into_iter()
22727            .find(|new_editor| *new_editor != test_editor_cx.entity())
22728            .expect("Should have one non-test editor now")
22729            .read(test_editor_cx)
22730            .text(test_editor_cx);
22731        assert_eq!(
22732            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22733            "Should use the range from the references response and not the GoToDefinition one"
22734        );
22735    });
22736}
22737
22738#[gpui::test]
22739async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22740    init_test(cx, |_| {});
22741    cx.update(|cx| {
22742        let mut editor_settings = EditorSettings::get_global(cx).clone();
22743        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22744        EditorSettings::override_global(editor_settings, cx);
22745    });
22746    let mut cx = EditorLspTestContext::new_rust(
22747        lsp::ServerCapabilities {
22748            definition_provider: Some(lsp::OneOf::Left(true)),
22749            references_provider: Some(lsp::OneOf::Left(true)),
22750            ..lsp::ServerCapabilities::default()
22751        },
22752        cx,
22753    )
22754    .await;
22755    let original_state = r#"fn one() {
22756        let mut a = ˇtwo();
22757    }
22758
22759    fn two() {}"#
22760        .unindent();
22761    cx.set_state(&original_state);
22762
22763    let mut go_to_definition = cx
22764        .lsp
22765        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22766            move |_, _| async move { Ok(None) },
22767        );
22768    let _references = cx
22769        .lsp
22770        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22771            panic!("Should not call for references with no go to definition fallback")
22772        });
22773
22774    let navigated = cx
22775        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22776        .await
22777        .expect("Failed to navigate to lookup references");
22778    go_to_definition
22779        .next()
22780        .await
22781        .expect("Should have called the go_to_definition handler");
22782
22783    assert_eq!(
22784        navigated,
22785        Navigated::No,
22786        "Should have navigated to references as a fallback after empty GoToDefinition response"
22787    );
22788    cx.assert_editor_state(&original_state);
22789    let editors = cx.update_workspace(|workspace, _, cx| {
22790        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22791    });
22792    cx.update_editor(|_, _, _| {
22793        assert_eq!(
22794            editors.len(),
22795            1,
22796            "After unsuccessful fallback, no other editor should have been opened"
22797        );
22798    });
22799}
22800
22801#[gpui::test]
22802async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22803    init_test(cx, |_| {});
22804    let mut cx = EditorLspTestContext::new_rust(
22805        lsp::ServerCapabilities {
22806            references_provider: Some(lsp::OneOf::Left(true)),
22807            ..lsp::ServerCapabilities::default()
22808        },
22809        cx,
22810    )
22811    .await;
22812
22813    cx.set_state(
22814        &r#"
22815        fn one() {
22816            let mut a = two();
22817        }
22818
22819        fn ˇtwo() {}"#
22820            .unindent(),
22821    );
22822    cx.lsp
22823        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22824            Ok(Some(vec![
22825                lsp::Location {
22826                    uri: params.text_document_position.text_document.uri.clone(),
22827                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22828                },
22829                lsp::Location {
22830                    uri: params.text_document_position.text_document.uri,
22831                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22832                },
22833            ]))
22834        });
22835    let navigated = cx
22836        .update_editor(|editor, window, cx| {
22837            editor.find_all_references(&FindAllReferences::default(), window, cx)
22838        })
22839        .unwrap()
22840        .await
22841        .expect("Failed to navigate to references");
22842    assert_eq!(
22843        navigated,
22844        Navigated::Yes,
22845        "Should have navigated to references from the FindAllReferences response"
22846    );
22847    cx.assert_editor_state(
22848        &r#"fn one() {
22849            let mut a = two();
22850        }
22851
22852        fn ˇtwo() {}"#
22853            .unindent(),
22854    );
22855
22856    let editors = cx.update_workspace(|workspace, _, cx| {
22857        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22858    });
22859    cx.update_editor(|_, _, _| {
22860        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22861    });
22862
22863    cx.set_state(
22864        &r#"fn one() {
22865            let mut a = ˇtwo();
22866        }
22867
22868        fn two() {}"#
22869            .unindent(),
22870    );
22871    let navigated = cx
22872        .update_editor(|editor, window, cx| {
22873            editor.find_all_references(&FindAllReferences::default(), window, cx)
22874        })
22875        .unwrap()
22876        .await
22877        .expect("Failed to navigate to references");
22878    assert_eq!(
22879        navigated,
22880        Navigated::Yes,
22881        "Should have navigated to references from the FindAllReferences response"
22882    );
22883    cx.assert_editor_state(
22884        &r#"fn one() {
22885            let mut a = ˇtwo();
22886        }
22887
22888        fn two() {}"#
22889            .unindent(),
22890    );
22891    let editors = cx.update_workspace(|workspace, _, cx| {
22892        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22893    });
22894    cx.update_editor(|_, _, _| {
22895        assert_eq!(
22896            editors.len(),
22897            2,
22898            "should have re-used the previous multibuffer"
22899        );
22900    });
22901
22902    cx.set_state(
22903        &r#"fn one() {
22904            let mut a = ˇtwo();
22905        }
22906        fn three() {}
22907        fn two() {}"#
22908            .unindent(),
22909    );
22910    cx.lsp
22911        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22912            Ok(Some(vec![
22913                lsp::Location {
22914                    uri: params.text_document_position.text_document.uri.clone(),
22915                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22916                },
22917                lsp::Location {
22918                    uri: params.text_document_position.text_document.uri,
22919                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22920                },
22921            ]))
22922        });
22923    let navigated = cx
22924        .update_editor(|editor, window, cx| {
22925            editor.find_all_references(&FindAllReferences::default(), window, cx)
22926        })
22927        .unwrap()
22928        .await
22929        .expect("Failed to navigate to references");
22930    assert_eq!(
22931        navigated,
22932        Navigated::Yes,
22933        "Should have navigated to references from the FindAllReferences response"
22934    );
22935    cx.assert_editor_state(
22936        &r#"fn one() {
22937                let mut a = ˇtwo();
22938            }
22939            fn three() {}
22940            fn two() {}"#
22941            .unindent(),
22942    );
22943    let editors = cx.update_workspace(|workspace, _, cx| {
22944        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22945    });
22946    cx.update_editor(|_, _, _| {
22947        assert_eq!(
22948            editors.len(),
22949            3,
22950            "should have used a new multibuffer as offsets changed"
22951        );
22952    });
22953}
22954#[gpui::test]
22955async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22956    init_test(cx, |_| {});
22957
22958    let language = Arc::new(Language::new(
22959        LanguageConfig::default(),
22960        Some(tree_sitter_rust::LANGUAGE.into()),
22961    ));
22962
22963    let text = r#"
22964        #[cfg(test)]
22965        mod tests() {
22966            #[test]
22967            fn runnable_1() {
22968                let a = 1;
22969            }
22970
22971            #[test]
22972            fn runnable_2() {
22973                let a = 1;
22974                let b = 2;
22975            }
22976        }
22977    "#
22978    .unindent();
22979
22980    let fs = FakeFs::new(cx.executor());
22981    fs.insert_file("/file.rs", Default::default()).await;
22982
22983    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22984    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22985    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22986    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22987    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22988
22989    let editor = cx.new_window_entity(|window, cx| {
22990        Editor::new(
22991            EditorMode::full(),
22992            multi_buffer,
22993            Some(project.clone()),
22994            window,
22995            cx,
22996        )
22997    });
22998
22999    editor.update_in(cx, |editor, window, cx| {
23000        let snapshot = editor.buffer().read(cx).snapshot(cx);
23001        editor.tasks.insert(
23002            (buffer.read(cx).remote_id(), 3),
23003            RunnableTasks {
23004                templates: vec![],
23005                offset: snapshot.anchor_before(MultiBufferOffset(43)),
23006                column: 0,
23007                extra_variables: HashMap::default(),
23008                context_range: BufferOffset(43)..BufferOffset(85),
23009            },
23010        );
23011        editor.tasks.insert(
23012            (buffer.read(cx).remote_id(), 8),
23013            RunnableTasks {
23014                templates: vec![],
23015                offset: snapshot.anchor_before(MultiBufferOffset(86)),
23016                column: 0,
23017                extra_variables: HashMap::default(),
23018                context_range: BufferOffset(86)..BufferOffset(191),
23019            },
23020        );
23021
23022        // Test finding task when cursor is inside function body
23023        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23024            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
23025        });
23026        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23027        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
23028
23029        // Test finding task when cursor is on function name
23030        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23031            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
23032        });
23033        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
23034        assert_eq!(row, 8, "Should find task when cursor is on function name");
23035    });
23036}
23037
23038#[gpui::test]
23039async fn test_folding_buffers(cx: &mut TestAppContext) {
23040    init_test(cx, |_| {});
23041
23042    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23043    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
23044    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
23045
23046    let fs = FakeFs::new(cx.executor());
23047    fs.insert_tree(
23048        path!("/a"),
23049        json!({
23050            "first.rs": sample_text_1,
23051            "second.rs": sample_text_2,
23052            "third.rs": sample_text_3,
23053        }),
23054    )
23055    .await;
23056    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23057    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23058    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23059    let worktree = project.update(cx, |project, cx| {
23060        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23061        assert_eq!(worktrees.len(), 1);
23062        worktrees.pop().unwrap()
23063    });
23064    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23065
23066    let buffer_1 = project
23067        .update(cx, |project, cx| {
23068            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23069        })
23070        .await
23071        .unwrap();
23072    let buffer_2 = project
23073        .update(cx, |project, cx| {
23074            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23075        })
23076        .await
23077        .unwrap();
23078    let buffer_3 = project
23079        .update(cx, |project, cx| {
23080            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23081        })
23082        .await
23083        .unwrap();
23084
23085    let multi_buffer = cx.new(|cx| {
23086        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23087        multi_buffer.push_excerpts(
23088            buffer_1.clone(),
23089            [
23090                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23091                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23092                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23093            ],
23094            cx,
23095        );
23096        multi_buffer.push_excerpts(
23097            buffer_2.clone(),
23098            [
23099                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23100                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23101                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23102            ],
23103            cx,
23104        );
23105        multi_buffer.push_excerpts(
23106            buffer_3.clone(),
23107            [
23108                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
23109                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
23110                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
23111            ],
23112            cx,
23113        );
23114        multi_buffer
23115    });
23116    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23117        Editor::new(
23118            EditorMode::full(),
23119            multi_buffer.clone(),
23120            Some(project.clone()),
23121            window,
23122            cx,
23123        )
23124    });
23125
23126    assert_eq!(
23127        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23128        "\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",
23129    );
23130
23131    multi_buffer_editor.update(cx, |editor, cx| {
23132        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23133    });
23134    assert_eq!(
23135        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23136        "\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",
23137        "After folding the first buffer, its text should not be displayed"
23138    );
23139
23140    multi_buffer_editor.update(cx, |editor, cx| {
23141        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23142    });
23143    assert_eq!(
23144        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23145        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
23146        "After folding the second buffer, its text should not be displayed"
23147    );
23148
23149    multi_buffer_editor.update(cx, |editor, cx| {
23150        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23151    });
23152    assert_eq!(
23153        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23154        "\n\n\n\n\n",
23155        "After folding the third buffer, its text should not be displayed"
23156    );
23157
23158    // Emulate selection inside the fold logic, that should work
23159    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23160        editor
23161            .snapshot(window, cx)
23162            .next_line_boundary(Point::new(0, 4));
23163    });
23164
23165    multi_buffer_editor.update(cx, |editor, cx| {
23166        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23167    });
23168    assert_eq!(
23169        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23170        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23171        "After unfolding the second buffer, its text should be displayed"
23172    );
23173
23174    // Typing inside of buffer 1 causes that buffer to be unfolded.
23175    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23176        assert_eq!(
23177            multi_buffer
23178                .read(cx)
23179                .snapshot(cx)
23180                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23181                .collect::<String>(),
23182            "bbbb"
23183        );
23184        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23185            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23186        });
23187        editor.handle_input("B", window, cx);
23188    });
23189
23190    assert_eq!(
23191        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23192        "\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",
23193        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23194    );
23195
23196    multi_buffer_editor.update(cx, |editor, cx| {
23197        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23198    });
23199    assert_eq!(
23200        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23201        "\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",
23202        "After unfolding the all buffers, all original text should be displayed"
23203    );
23204}
23205
23206#[gpui::test]
23207async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23208    init_test(cx, |_| {});
23209
23210    let sample_text_1 = "1111\n2222\n3333".to_string();
23211    let sample_text_2 = "4444\n5555\n6666".to_string();
23212    let sample_text_3 = "7777\n8888\n9999".to_string();
23213
23214    let fs = FakeFs::new(cx.executor());
23215    fs.insert_tree(
23216        path!("/a"),
23217        json!({
23218            "first.rs": sample_text_1,
23219            "second.rs": sample_text_2,
23220            "third.rs": sample_text_3,
23221        }),
23222    )
23223    .await;
23224    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23225    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23226    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23227    let worktree = project.update(cx, |project, cx| {
23228        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23229        assert_eq!(worktrees.len(), 1);
23230        worktrees.pop().unwrap()
23231    });
23232    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23233
23234    let buffer_1 = project
23235        .update(cx, |project, cx| {
23236            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23237        })
23238        .await
23239        .unwrap();
23240    let buffer_2 = project
23241        .update(cx, |project, cx| {
23242            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23243        })
23244        .await
23245        .unwrap();
23246    let buffer_3 = project
23247        .update(cx, |project, cx| {
23248            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23249        })
23250        .await
23251        .unwrap();
23252
23253    let multi_buffer = cx.new(|cx| {
23254        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23255        multi_buffer.push_excerpts(
23256            buffer_1.clone(),
23257            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23258            cx,
23259        );
23260        multi_buffer.push_excerpts(
23261            buffer_2.clone(),
23262            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23263            cx,
23264        );
23265        multi_buffer.push_excerpts(
23266            buffer_3.clone(),
23267            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23268            cx,
23269        );
23270        multi_buffer
23271    });
23272
23273    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23274        Editor::new(
23275            EditorMode::full(),
23276            multi_buffer,
23277            Some(project.clone()),
23278            window,
23279            cx,
23280        )
23281    });
23282
23283    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23284    assert_eq!(
23285        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23286        full_text,
23287    );
23288
23289    multi_buffer_editor.update(cx, |editor, cx| {
23290        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23291    });
23292    assert_eq!(
23293        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23294        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23295        "After folding the first buffer, its text should not be displayed"
23296    );
23297
23298    multi_buffer_editor.update(cx, |editor, cx| {
23299        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23300    });
23301
23302    assert_eq!(
23303        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23304        "\n\n\n\n\n\n7777\n8888\n9999",
23305        "After folding the second buffer, its text should not be displayed"
23306    );
23307
23308    multi_buffer_editor.update(cx, |editor, cx| {
23309        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23310    });
23311    assert_eq!(
23312        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23313        "\n\n\n\n\n",
23314        "After folding the third buffer, its text should not be displayed"
23315    );
23316
23317    multi_buffer_editor.update(cx, |editor, cx| {
23318        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23319    });
23320    assert_eq!(
23321        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23322        "\n\n\n\n4444\n5555\n6666\n\n",
23323        "After unfolding the second buffer, its text should be displayed"
23324    );
23325
23326    multi_buffer_editor.update(cx, |editor, cx| {
23327        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23328    });
23329    assert_eq!(
23330        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23331        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23332        "After unfolding the first buffer, its text should be displayed"
23333    );
23334
23335    multi_buffer_editor.update(cx, |editor, cx| {
23336        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23337    });
23338    assert_eq!(
23339        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23340        full_text,
23341        "After unfolding all buffers, all original text should be displayed"
23342    );
23343}
23344
23345#[gpui::test]
23346async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23347    init_test(cx, |_| {});
23348
23349    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23350
23351    let fs = FakeFs::new(cx.executor());
23352    fs.insert_tree(
23353        path!("/a"),
23354        json!({
23355            "main.rs": sample_text,
23356        }),
23357    )
23358    .await;
23359    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23360    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23361    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23362    let worktree = project.update(cx, |project, cx| {
23363        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23364        assert_eq!(worktrees.len(), 1);
23365        worktrees.pop().unwrap()
23366    });
23367    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23368
23369    let buffer_1 = project
23370        .update(cx, |project, cx| {
23371            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23372        })
23373        .await
23374        .unwrap();
23375
23376    let multi_buffer = cx.new(|cx| {
23377        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23378        multi_buffer.push_excerpts(
23379            buffer_1.clone(),
23380            [ExcerptRange::new(
23381                Point::new(0, 0)
23382                    ..Point::new(
23383                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23384                        0,
23385                    ),
23386            )],
23387            cx,
23388        );
23389        multi_buffer
23390    });
23391    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23392        Editor::new(
23393            EditorMode::full(),
23394            multi_buffer,
23395            Some(project.clone()),
23396            window,
23397            cx,
23398        )
23399    });
23400
23401    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23402    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23403        enum TestHighlight {}
23404        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23405        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23406        editor.highlight_text::<TestHighlight>(
23407            vec![highlight_range.clone()],
23408            HighlightStyle::color(Hsla::green()),
23409            cx,
23410        );
23411        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23412            s.select_ranges(Some(highlight_range))
23413        });
23414    });
23415
23416    let full_text = format!("\n\n{sample_text}");
23417    assert_eq!(
23418        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23419        full_text,
23420    );
23421}
23422
23423#[gpui::test]
23424async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23425    init_test(cx, |_| {});
23426    cx.update(|cx| {
23427        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23428            "keymaps/default-linux.json",
23429            cx,
23430        )
23431        .unwrap();
23432        cx.bind_keys(default_key_bindings);
23433    });
23434
23435    let (editor, cx) = cx.add_window_view(|window, cx| {
23436        let multi_buffer = MultiBuffer::build_multi(
23437            [
23438                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23439                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23440                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23441                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23442            ],
23443            cx,
23444        );
23445        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23446
23447        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23448        // fold all but the second buffer, so that we test navigating between two
23449        // adjacent folded buffers, as well as folded buffers at the start and
23450        // end the multibuffer
23451        editor.fold_buffer(buffer_ids[0], cx);
23452        editor.fold_buffer(buffer_ids[2], cx);
23453        editor.fold_buffer(buffer_ids[3], cx);
23454
23455        editor
23456    });
23457    cx.simulate_resize(size(px(1000.), px(1000.)));
23458
23459    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23460    cx.assert_excerpts_with_selections(indoc! {"
23461        [EXCERPT]
23462        ˇ[FOLDED]
23463        [EXCERPT]
23464        a1
23465        b1
23466        [EXCERPT]
23467        [FOLDED]
23468        [EXCERPT]
23469        [FOLDED]
23470        "
23471    });
23472    cx.simulate_keystroke("down");
23473    cx.assert_excerpts_with_selections(indoc! {"
23474        [EXCERPT]
23475        [FOLDED]
23476        [EXCERPT]
23477        ˇa1
23478        b1
23479        [EXCERPT]
23480        [FOLDED]
23481        [EXCERPT]
23482        [FOLDED]
23483        "
23484    });
23485    cx.simulate_keystroke("down");
23486    cx.assert_excerpts_with_selections(indoc! {"
23487        [EXCERPT]
23488        [FOLDED]
23489        [EXCERPT]
23490        a1
23491        ˇb1
23492        [EXCERPT]
23493        [FOLDED]
23494        [EXCERPT]
23495        [FOLDED]
23496        "
23497    });
23498    cx.simulate_keystroke("down");
23499    cx.assert_excerpts_with_selections(indoc! {"
23500        [EXCERPT]
23501        [FOLDED]
23502        [EXCERPT]
23503        a1
23504        b1
23505        ˇ[EXCERPT]
23506        [FOLDED]
23507        [EXCERPT]
23508        [FOLDED]
23509        "
23510    });
23511    cx.simulate_keystroke("down");
23512    cx.assert_excerpts_with_selections(indoc! {"
23513        [EXCERPT]
23514        [FOLDED]
23515        [EXCERPT]
23516        a1
23517        b1
23518        [EXCERPT]
23519        ˇ[FOLDED]
23520        [EXCERPT]
23521        [FOLDED]
23522        "
23523    });
23524    for _ in 0..5 {
23525        cx.simulate_keystroke("down");
23526        cx.assert_excerpts_with_selections(indoc! {"
23527            [EXCERPT]
23528            [FOLDED]
23529            [EXCERPT]
23530            a1
23531            b1
23532            [EXCERPT]
23533            [FOLDED]
23534            [EXCERPT]
23535            ˇ[FOLDED]
23536            "
23537        });
23538    }
23539
23540    cx.simulate_keystroke("up");
23541    cx.assert_excerpts_with_selections(indoc! {"
23542        [EXCERPT]
23543        [FOLDED]
23544        [EXCERPT]
23545        a1
23546        b1
23547        [EXCERPT]
23548        ˇ[FOLDED]
23549        [EXCERPT]
23550        [FOLDED]
23551        "
23552    });
23553    cx.simulate_keystroke("up");
23554    cx.assert_excerpts_with_selections(indoc! {"
23555        [EXCERPT]
23556        [FOLDED]
23557        [EXCERPT]
23558        a1
23559        b1
23560        ˇ[EXCERPT]
23561        [FOLDED]
23562        [EXCERPT]
23563        [FOLDED]
23564        "
23565    });
23566    cx.simulate_keystroke("up");
23567    cx.assert_excerpts_with_selections(indoc! {"
23568        [EXCERPT]
23569        [FOLDED]
23570        [EXCERPT]
23571        a1
23572        ˇb1
23573        [EXCERPT]
23574        [FOLDED]
23575        [EXCERPT]
23576        [FOLDED]
23577        "
23578    });
23579    cx.simulate_keystroke("up");
23580    cx.assert_excerpts_with_selections(indoc! {"
23581        [EXCERPT]
23582        [FOLDED]
23583        [EXCERPT]
23584        ˇa1
23585        b1
23586        [EXCERPT]
23587        [FOLDED]
23588        [EXCERPT]
23589        [FOLDED]
23590        "
23591    });
23592    for _ in 0..5 {
23593        cx.simulate_keystroke("up");
23594        cx.assert_excerpts_with_selections(indoc! {"
23595            [EXCERPT]
23596            ˇ[FOLDED]
23597            [EXCERPT]
23598            a1
23599            b1
23600            [EXCERPT]
23601            [FOLDED]
23602            [EXCERPT]
23603            [FOLDED]
23604            "
23605        });
23606    }
23607}
23608
23609#[gpui::test]
23610async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23611    init_test(cx, |_| {});
23612
23613    // Simple insertion
23614    assert_highlighted_edits(
23615        "Hello, world!",
23616        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23617        true,
23618        cx,
23619        |highlighted_edits, cx| {
23620            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23621            assert_eq!(highlighted_edits.highlights.len(), 1);
23622            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23623            assert_eq!(
23624                highlighted_edits.highlights[0].1.background_color,
23625                Some(cx.theme().status().created_background)
23626            );
23627        },
23628    )
23629    .await;
23630
23631    // Replacement
23632    assert_highlighted_edits(
23633        "This is a test.",
23634        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23635        false,
23636        cx,
23637        |highlighted_edits, cx| {
23638            assert_eq!(highlighted_edits.text, "That is a test.");
23639            assert_eq!(highlighted_edits.highlights.len(), 1);
23640            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23641            assert_eq!(
23642                highlighted_edits.highlights[0].1.background_color,
23643                Some(cx.theme().status().created_background)
23644            );
23645        },
23646    )
23647    .await;
23648
23649    // Multiple edits
23650    assert_highlighted_edits(
23651        "Hello, world!",
23652        vec![
23653            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23654            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23655        ],
23656        false,
23657        cx,
23658        |highlighted_edits, cx| {
23659            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23660            assert_eq!(highlighted_edits.highlights.len(), 2);
23661            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23662            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23663            assert_eq!(
23664                highlighted_edits.highlights[0].1.background_color,
23665                Some(cx.theme().status().created_background)
23666            );
23667            assert_eq!(
23668                highlighted_edits.highlights[1].1.background_color,
23669                Some(cx.theme().status().created_background)
23670            );
23671        },
23672    )
23673    .await;
23674
23675    // Multiple lines with edits
23676    assert_highlighted_edits(
23677        "First line\nSecond line\nThird line\nFourth line",
23678        vec![
23679            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23680            (
23681                Point::new(2, 0)..Point::new(2, 10),
23682                "New third line".to_string(),
23683            ),
23684            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23685        ],
23686        false,
23687        cx,
23688        |highlighted_edits, cx| {
23689            assert_eq!(
23690                highlighted_edits.text,
23691                "Second modified\nNew third line\nFourth updated line"
23692            );
23693            assert_eq!(highlighted_edits.highlights.len(), 3);
23694            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23695            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23696            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23697            for highlight in &highlighted_edits.highlights {
23698                assert_eq!(
23699                    highlight.1.background_color,
23700                    Some(cx.theme().status().created_background)
23701                );
23702            }
23703        },
23704    )
23705    .await;
23706}
23707
23708#[gpui::test]
23709async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23710    init_test(cx, |_| {});
23711
23712    // Deletion
23713    assert_highlighted_edits(
23714        "Hello, world!",
23715        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23716        true,
23717        cx,
23718        |highlighted_edits, cx| {
23719            assert_eq!(highlighted_edits.text, "Hello, world!");
23720            assert_eq!(highlighted_edits.highlights.len(), 1);
23721            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23722            assert_eq!(
23723                highlighted_edits.highlights[0].1.background_color,
23724                Some(cx.theme().status().deleted_background)
23725            );
23726        },
23727    )
23728    .await;
23729
23730    // Insertion
23731    assert_highlighted_edits(
23732        "Hello, world!",
23733        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23734        true,
23735        cx,
23736        |highlighted_edits, cx| {
23737            assert_eq!(highlighted_edits.highlights.len(), 1);
23738            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23739            assert_eq!(
23740                highlighted_edits.highlights[0].1.background_color,
23741                Some(cx.theme().status().created_background)
23742            );
23743        },
23744    )
23745    .await;
23746}
23747
23748async fn assert_highlighted_edits(
23749    text: &str,
23750    edits: Vec<(Range<Point>, String)>,
23751    include_deletions: bool,
23752    cx: &mut TestAppContext,
23753    assertion_fn: impl Fn(HighlightedText, &App),
23754) {
23755    let window = cx.add_window(|window, cx| {
23756        let buffer = MultiBuffer::build_simple(text, cx);
23757        Editor::new(EditorMode::full(), buffer, None, window, cx)
23758    });
23759    let cx = &mut VisualTestContext::from_window(*window, cx);
23760
23761    let (buffer, snapshot) = window
23762        .update(cx, |editor, _window, cx| {
23763            (
23764                editor.buffer().clone(),
23765                editor.buffer().read(cx).snapshot(cx),
23766            )
23767        })
23768        .unwrap();
23769
23770    let edits = edits
23771        .into_iter()
23772        .map(|(range, edit)| {
23773            (
23774                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23775                edit,
23776            )
23777        })
23778        .collect::<Vec<_>>();
23779
23780    let text_anchor_edits = edits
23781        .clone()
23782        .into_iter()
23783        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23784        .collect::<Vec<_>>();
23785
23786    let edit_preview = window
23787        .update(cx, |_, _window, cx| {
23788            buffer
23789                .read(cx)
23790                .as_singleton()
23791                .unwrap()
23792                .read(cx)
23793                .preview_edits(text_anchor_edits.into(), cx)
23794        })
23795        .unwrap()
23796        .await;
23797
23798    cx.update(|_window, cx| {
23799        let highlighted_edits = edit_prediction_edit_text(
23800            snapshot.as_singleton().unwrap().2,
23801            &edits,
23802            &edit_preview,
23803            include_deletions,
23804            cx,
23805        );
23806        assertion_fn(highlighted_edits, cx)
23807    });
23808}
23809
23810#[track_caller]
23811fn assert_breakpoint(
23812    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23813    path: &Arc<Path>,
23814    expected: Vec<(u32, Breakpoint)>,
23815) {
23816    if expected.is_empty() {
23817        assert!(!breakpoints.contains_key(path), "{}", path.display());
23818    } else {
23819        let mut breakpoint = breakpoints
23820            .get(path)
23821            .unwrap()
23822            .iter()
23823            .map(|breakpoint| {
23824                (
23825                    breakpoint.row,
23826                    Breakpoint {
23827                        message: breakpoint.message.clone(),
23828                        state: breakpoint.state,
23829                        condition: breakpoint.condition.clone(),
23830                        hit_condition: breakpoint.hit_condition.clone(),
23831                    },
23832                )
23833            })
23834            .collect::<Vec<_>>();
23835
23836        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23837
23838        assert_eq!(expected, breakpoint);
23839    }
23840}
23841
23842fn add_log_breakpoint_at_cursor(
23843    editor: &mut Editor,
23844    log_message: &str,
23845    window: &mut Window,
23846    cx: &mut Context<Editor>,
23847) {
23848    let (anchor, bp) = editor
23849        .breakpoints_at_cursors(window, cx)
23850        .first()
23851        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23852        .unwrap_or_else(|| {
23853            let snapshot = editor.snapshot(window, cx);
23854            let cursor_position: Point =
23855                editor.selections.newest(&snapshot.display_snapshot).head();
23856
23857            let breakpoint_position = snapshot
23858                .buffer_snapshot()
23859                .anchor_before(Point::new(cursor_position.row, 0));
23860
23861            (breakpoint_position, Breakpoint::new_log(log_message))
23862        });
23863
23864    editor.edit_breakpoint_at_anchor(
23865        anchor,
23866        bp,
23867        BreakpointEditAction::EditLogMessage(log_message.into()),
23868        cx,
23869    );
23870}
23871
23872#[gpui::test]
23873async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23874    init_test(cx, |_| {});
23875
23876    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23877    let fs = FakeFs::new(cx.executor());
23878    fs.insert_tree(
23879        path!("/a"),
23880        json!({
23881            "main.rs": sample_text,
23882        }),
23883    )
23884    .await;
23885    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23886    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23887    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23888
23889    let fs = FakeFs::new(cx.executor());
23890    fs.insert_tree(
23891        path!("/a"),
23892        json!({
23893            "main.rs": sample_text,
23894        }),
23895    )
23896    .await;
23897    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23898    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23899    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23900    let worktree_id = workspace
23901        .update(cx, |workspace, _window, cx| {
23902            workspace.project().update(cx, |project, cx| {
23903                project.worktrees(cx).next().unwrap().read(cx).id()
23904            })
23905        })
23906        .unwrap();
23907
23908    let buffer = project
23909        .update(cx, |project, cx| {
23910            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23911        })
23912        .await
23913        .unwrap();
23914
23915    let (editor, cx) = cx.add_window_view(|window, cx| {
23916        Editor::new(
23917            EditorMode::full(),
23918            MultiBuffer::build_from_buffer(buffer, cx),
23919            Some(project.clone()),
23920            window,
23921            cx,
23922        )
23923    });
23924
23925    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23926    let abs_path = project.read_with(cx, |project, cx| {
23927        project
23928            .absolute_path(&project_path, cx)
23929            .map(Arc::from)
23930            .unwrap()
23931    });
23932
23933    // assert we can add breakpoint on the first line
23934    editor.update_in(cx, |editor, window, cx| {
23935        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23936        editor.move_to_end(&MoveToEnd, window, cx);
23937        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23938    });
23939
23940    let breakpoints = editor.update(cx, |editor, cx| {
23941        editor
23942            .breakpoint_store()
23943            .as_ref()
23944            .unwrap()
23945            .read(cx)
23946            .all_source_breakpoints(cx)
23947    });
23948
23949    assert_eq!(1, breakpoints.len());
23950    assert_breakpoint(
23951        &breakpoints,
23952        &abs_path,
23953        vec![
23954            (0, Breakpoint::new_standard()),
23955            (3, Breakpoint::new_standard()),
23956        ],
23957    );
23958
23959    editor.update_in(cx, |editor, window, cx| {
23960        editor.move_to_beginning(&MoveToBeginning, window, cx);
23961        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23962    });
23963
23964    let breakpoints = editor.update(cx, |editor, cx| {
23965        editor
23966            .breakpoint_store()
23967            .as_ref()
23968            .unwrap()
23969            .read(cx)
23970            .all_source_breakpoints(cx)
23971    });
23972
23973    assert_eq!(1, breakpoints.len());
23974    assert_breakpoint(
23975        &breakpoints,
23976        &abs_path,
23977        vec![(3, Breakpoint::new_standard())],
23978    );
23979
23980    editor.update_in(cx, |editor, window, cx| {
23981        editor.move_to_end(&MoveToEnd, window, cx);
23982        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23983    });
23984
23985    let breakpoints = editor.update(cx, |editor, cx| {
23986        editor
23987            .breakpoint_store()
23988            .as_ref()
23989            .unwrap()
23990            .read(cx)
23991            .all_source_breakpoints(cx)
23992    });
23993
23994    assert_eq!(0, breakpoints.len());
23995    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23996}
23997
23998#[gpui::test]
23999async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
24000    init_test(cx, |_| {});
24001
24002    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24003
24004    let fs = FakeFs::new(cx.executor());
24005    fs.insert_tree(
24006        path!("/a"),
24007        json!({
24008            "main.rs": sample_text,
24009        }),
24010    )
24011    .await;
24012    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24013    let (workspace, cx) =
24014        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24015
24016    let worktree_id = workspace.update(cx, |workspace, cx| {
24017        workspace.project().update(cx, |project, cx| {
24018            project.worktrees(cx).next().unwrap().read(cx).id()
24019        })
24020    });
24021
24022    let buffer = project
24023        .update(cx, |project, cx| {
24024            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24025        })
24026        .await
24027        .unwrap();
24028
24029    let (editor, cx) = cx.add_window_view(|window, cx| {
24030        Editor::new(
24031            EditorMode::full(),
24032            MultiBuffer::build_from_buffer(buffer, cx),
24033            Some(project.clone()),
24034            window,
24035            cx,
24036        )
24037    });
24038
24039    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24040    let abs_path = project.read_with(cx, |project, cx| {
24041        project
24042            .absolute_path(&project_path, cx)
24043            .map(Arc::from)
24044            .unwrap()
24045    });
24046
24047    editor.update_in(cx, |editor, window, cx| {
24048        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24049    });
24050
24051    let breakpoints = editor.update(cx, |editor, cx| {
24052        editor
24053            .breakpoint_store()
24054            .as_ref()
24055            .unwrap()
24056            .read(cx)
24057            .all_source_breakpoints(cx)
24058    });
24059
24060    assert_breakpoint(
24061        &breakpoints,
24062        &abs_path,
24063        vec![(0, Breakpoint::new_log("hello world"))],
24064    );
24065
24066    // Removing a log message from a log breakpoint should remove it
24067    editor.update_in(cx, |editor, window, cx| {
24068        add_log_breakpoint_at_cursor(editor, "", window, cx);
24069    });
24070
24071    let breakpoints = editor.update(cx, |editor, cx| {
24072        editor
24073            .breakpoint_store()
24074            .as_ref()
24075            .unwrap()
24076            .read(cx)
24077            .all_source_breakpoints(cx)
24078    });
24079
24080    assert_breakpoint(&breakpoints, &abs_path, vec![]);
24081
24082    editor.update_in(cx, |editor, window, cx| {
24083        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24084        editor.move_to_end(&MoveToEnd, window, cx);
24085        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24086        // Not adding a log message to a standard breakpoint shouldn't remove it
24087        add_log_breakpoint_at_cursor(editor, "", window, cx);
24088    });
24089
24090    let breakpoints = editor.update(cx, |editor, cx| {
24091        editor
24092            .breakpoint_store()
24093            .as_ref()
24094            .unwrap()
24095            .read(cx)
24096            .all_source_breakpoints(cx)
24097    });
24098
24099    assert_breakpoint(
24100        &breakpoints,
24101        &abs_path,
24102        vec![
24103            (0, Breakpoint::new_standard()),
24104            (3, Breakpoint::new_standard()),
24105        ],
24106    );
24107
24108    editor.update_in(cx, |editor, window, cx| {
24109        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
24110    });
24111
24112    let breakpoints = editor.update(cx, |editor, cx| {
24113        editor
24114            .breakpoint_store()
24115            .as_ref()
24116            .unwrap()
24117            .read(cx)
24118            .all_source_breakpoints(cx)
24119    });
24120
24121    assert_breakpoint(
24122        &breakpoints,
24123        &abs_path,
24124        vec![
24125            (0, Breakpoint::new_standard()),
24126            (3, Breakpoint::new_log("hello world")),
24127        ],
24128    );
24129
24130    editor.update_in(cx, |editor, window, cx| {
24131        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
24132    });
24133
24134    let breakpoints = editor.update(cx, |editor, cx| {
24135        editor
24136            .breakpoint_store()
24137            .as_ref()
24138            .unwrap()
24139            .read(cx)
24140            .all_source_breakpoints(cx)
24141    });
24142
24143    assert_breakpoint(
24144        &breakpoints,
24145        &abs_path,
24146        vec![
24147            (0, Breakpoint::new_standard()),
24148            (3, Breakpoint::new_log("hello Earth!!")),
24149        ],
24150    );
24151}
24152
24153/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24154/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24155/// or when breakpoints were placed out of order. This tests for a regression too
24156#[gpui::test]
24157async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24158    init_test(cx, |_| {});
24159
24160    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24161    let fs = FakeFs::new(cx.executor());
24162    fs.insert_tree(
24163        path!("/a"),
24164        json!({
24165            "main.rs": sample_text,
24166        }),
24167    )
24168    .await;
24169    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24170    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24171    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24172
24173    let fs = FakeFs::new(cx.executor());
24174    fs.insert_tree(
24175        path!("/a"),
24176        json!({
24177            "main.rs": sample_text,
24178        }),
24179    )
24180    .await;
24181    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24182    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24183    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24184    let worktree_id = workspace
24185        .update(cx, |workspace, _window, cx| {
24186            workspace.project().update(cx, |project, cx| {
24187                project.worktrees(cx).next().unwrap().read(cx).id()
24188            })
24189        })
24190        .unwrap();
24191
24192    let buffer = project
24193        .update(cx, |project, cx| {
24194            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24195        })
24196        .await
24197        .unwrap();
24198
24199    let (editor, cx) = cx.add_window_view(|window, cx| {
24200        Editor::new(
24201            EditorMode::full(),
24202            MultiBuffer::build_from_buffer(buffer, cx),
24203            Some(project.clone()),
24204            window,
24205            cx,
24206        )
24207    });
24208
24209    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24210    let abs_path = project.read_with(cx, |project, cx| {
24211        project
24212            .absolute_path(&project_path, cx)
24213            .map(Arc::from)
24214            .unwrap()
24215    });
24216
24217    // assert we can add breakpoint on the first line
24218    editor.update_in(cx, |editor, window, cx| {
24219        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24220        editor.move_to_end(&MoveToEnd, window, cx);
24221        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24222        editor.move_up(&MoveUp, window, cx);
24223        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24224    });
24225
24226    let breakpoints = editor.update(cx, |editor, cx| {
24227        editor
24228            .breakpoint_store()
24229            .as_ref()
24230            .unwrap()
24231            .read(cx)
24232            .all_source_breakpoints(cx)
24233    });
24234
24235    assert_eq!(1, breakpoints.len());
24236    assert_breakpoint(
24237        &breakpoints,
24238        &abs_path,
24239        vec![
24240            (0, Breakpoint::new_standard()),
24241            (2, Breakpoint::new_standard()),
24242            (3, Breakpoint::new_standard()),
24243        ],
24244    );
24245
24246    editor.update_in(cx, |editor, window, cx| {
24247        editor.move_to_beginning(&MoveToBeginning, window, cx);
24248        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24249        editor.move_to_end(&MoveToEnd, window, cx);
24250        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24251        // Disabling a breakpoint that doesn't exist should do nothing
24252        editor.move_up(&MoveUp, window, cx);
24253        editor.move_up(&MoveUp, window, cx);
24254        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24255    });
24256
24257    let breakpoints = editor.update(cx, |editor, cx| {
24258        editor
24259            .breakpoint_store()
24260            .as_ref()
24261            .unwrap()
24262            .read(cx)
24263            .all_source_breakpoints(cx)
24264    });
24265
24266    let disable_breakpoint = {
24267        let mut bp = Breakpoint::new_standard();
24268        bp.state = BreakpointState::Disabled;
24269        bp
24270    };
24271
24272    assert_eq!(1, breakpoints.len());
24273    assert_breakpoint(
24274        &breakpoints,
24275        &abs_path,
24276        vec![
24277            (0, disable_breakpoint.clone()),
24278            (2, Breakpoint::new_standard()),
24279            (3, disable_breakpoint.clone()),
24280        ],
24281    );
24282
24283    editor.update_in(cx, |editor, window, cx| {
24284        editor.move_to_beginning(&MoveToBeginning, window, cx);
24285        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24286        editor.move_to_end(&MoveToEnd, window, cx);
24287        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24288        editor.move_up(&MoveUp, window, cx);
24289        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24290    });
24291
24292    let breakpoints = editor.update(cx, |editor, cx| {
24293        editor
24294            .breakpoint_store()
24295            .as_ref()
24296            .unwrap()
24297            .read(cx)
24298            .all_source_breakpoints(cx)
24299    });
24300
24301    assert_eq!(1, breakpoints.len());
24302    assert_breakpoint(
24303        &breakpoints,
24304        &abs_path,
24305        vec![
24306            (0, Breakpoint::new_standard()),
24307            (2, disable_breakpoint),
24308            (3, Breakpoint::new_standard()),
24309        ],
24310    );
24311}
24312
24313#[gpui::test]
24314async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24315    init_test(cx, |_| {});
24316    let capabilities = lsp::ServerCapabilities {
24317        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24318            prepare_provider: Some(true),
24319            work_done_progress_options: Default::default(),
24320        })),
24321        ..Default::default()
24322    };
24323    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24324
24325    cx.set_state(indoc! {"
24326        struct Fˇoo {}
24327    "});
24328
24329    cx.update_editor(|editor, _, cx| {
24330        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24331        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24332        editor.highlight_background::<DocumentHighlightRead>(
24333            &[highlight_range],
24334            |_, theme| theme.colors().editor_document_highlight_read_background,
24335            cx,
24336        );
24337    });
24338
24339    let mut prepare_rename_handler = cx
24340        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24341            move |_, _, _| async move {
24342                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24343                    start: lsp::Position {
24344                        line: 0,
24345                        character: 7,
24346                    },
24347                    end: lsp::Position {
24348                        line: 0,
24349                        character: 10,
24350                    },
24351                })))
24352            },
24353        );
24354    let prepare_rename_task = cx
24355        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24356        .expect("Prepare rename was not started");
24357    prepare_rename_handler.next().await.unwrap();
24358    prepare_rename_task.await.expect("Prepare rename failed");
24359
24360    let mut rename_handler =
24361        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24362            let edit = lsp::TextEdit {
24363                range: lsp::Range {
24364                    start: lsp::Position {
24365                        line: 0,
24366                        character: 7,
24367                    },
24368                    end: lsp::Position {
24369                        line: 0,
24370                        character: 10,
24371                    },
24372                },
24373                new_text: "FooRenamed".to_string(),
24374            };
24375            Ok(Some(lsp::WorkspaceEdit::new(
24376                // Specify the same edit twice
24377                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24378            )))
24379        });
24380    let rename_task = cx
24381        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24382        .expect("Confirm rename was not started");
24383    rename_handler.next().await.unwrap();
24384    rename_task.await.expect("Confirm rename failed");
24385    cx.run_until_parked();
24386
24387    // Despite two edits, only one is actually applied as those are identical
24388    cx.assert_editor_state(indoc! {"
24389        struct FooRenamedˇ {}
24390    "});
24391}
24392
24393#[gpui::test]
24394async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24395    init_test(cx, |_| {});
24396    // These capabilities indicate that the server does not support prepare rename.
24397    let capabilities = lsp::ServerCapabilities {
24398        rename_provider: Some(lsp::OneOf::Left(true)),
24399        ..Default::default()
24400    };
24401    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24402
24403    cx.set_state(indoc! {"
24404        struct Fˇoo {}
24405    "});
24406
24407    cx.update_editor(|editor, _window, cx| {
24408        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24409        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24410        editor.highlight_background::<DocumentHighlightRead>(
24411            &[highlight_range],
24412            |_, theme| theme.colors().editor_document_highlight_read_background,
24413            cx,
24414        );
24415    });
24416
24417    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24418        .expect("Prepare rename was not started")
24419        .await
24420        .expect("Prepare rename failed");
24421
24422    let mut rename_handler =
24423        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24424            let edit = lsp::TextEdit {
24425                range: lsp::Range {
24426                    start: lsp::Position {
24427                        line: 0,
24428                        character: 7,
24429                    },
24430                    end: lsp::Position {
24431                        line: 0,
24432                        character: 10,
24433                    },
24434                },
24435                new_text: "FooRenamed".to_string(),
24436            };
24437            Ok(Some(lsp::WorkspaceEdit::new(
24438                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24439            )))
24440        });
24441    let rename_task = cx
24442        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24443        .expect("Confirm rename was not started");
24444    rename_handler.next().await.unwrap();
24445    rename_task.await.expect("Confirm rename failed");
24446    cx.run_until_parked();
24447
24448    // Correct range is renamed, as `surrounding_word` is used to find it.
24449    cx.assert_editor_state(indoc! {"
24450        struct FooRenamedˇ {}
24451    "});
24452}
24453
24454#[gpui::test]
24455async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24456    init_test(cx, |_| {});
24457    let mut cx = EditorTestContext::new(cx).await;
24458
24459    let language = Arc::new(
24460        Language::new(
24461            LanguageConfig::default(),
24462            Some(tree_sitter_html::LANGUAGE.into()),
24463        )
24464        .with_brackets_query(
24465            r#"
24466            ("<" @open "/>" @close)
24467            ("</" @open ">" @close)
24468            ("<" @open ">" @close)
24469            ("\"" @open "\"" @close)
24470            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24471        "#,
24472        )
24473        .unwrap(),
24474    );
24475    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24476
24477    cx.set_state(indoc! {"
24478        <span>ˇ</span>
24479    "});
24480    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24481    cx.assert_editor_state(indoc! {"
24482        <span>
24483        ˇ
24484        </span>
24485    "});
24486
24487    cx.set_state(indoc! {"
24488        <span><span></span>ˇ</span>
24489    "});
24490    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24491    cx.assert_editor_state(indoc! {"
24492        <span><span></span>
24493        ˇ</span>
24494    "});
24495
24496    cx.set_state(indoc! {"
24497        <span>ˇ
24498        </span>
24499    "});
24500    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24501    cx.assert_editor_state(indoc! {"
24502        <span>
24503        ˇ
24504        </span>
24505    "});
24506}
24507
24508#[gpui::test(iterations = 10)]
24509async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24510    init_test(cx, |_| {});
24511
24512    let fs = FakeFs::new(cx.executor());
24513    fs.insert_tree(
24514        path!("/dir"),
24515        json!({
24516            "a.ts": "a",
24517        }),
24518    )
24519    .await;
24520
24521    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24522    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24523    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24524
24525    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24526    language_registry.add(Arc::new(Language::new(
24527        LanguageConfig {
24528            name: "TypeScript".into(),
24529            matcher: LanguageMatcher {
24530                path_suffixes: vec!["ts".to_string()],
24531                ..Default::default()
24532            },
24533            ..Default::default()
24534        },
24535        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24536    )));
24537    let mut fake_language_servers = language_registry.register_fake_lsp(
24538        "TypeScript",
24539        FakeLspAdapter {
24540            capabilities: lsp::ServerCapabilities {
24541                code_lens_provider: Some(lsp::CodeLensOptions {
24542                    resolve_provider: Some(true),
24543                }),
24544                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24545                    commands: vec!["_the/command".to_string()],
24546                    ..lsp::ExecuteCommandOptions::default()
24547                }),
24548                ..lsp::ServerCapabilities::default()
24549            },
24550            ..FakeLspAdapter::default()
24551        },
24552    );
24553
24554    let editor = workspace
24555        .update(cx, |workspace, window, cx| {
24556            workspace.open_abs_path(
24557                PathBuf::from(path!("/dir/a.ts")),
24558                OpenOptions::default(),
24559                window,
24560                cx,
24561            )
24562        })
24563        .unwrap()
24564        .await
24565        .unwrap()
24566        .downcast::<Editor>()
24567        .unwrap();
24568    cx.executor().run_until_parked();
24569
24570    let fake_server = fake_language_servers.next().await.unwrap();
24571
24572    let buffer = editor.update(cx, |editor, cx| {
24573        editor
24574            .buffer()
24575            .read(cx)
24576            .as_singleton()
24577            .expect("have opened a single file by path")
24578    });
24579
24580    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24581    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24582    drop(buffer_snapshot);
24583    let actions = cx
24584        .update_window(*workspace, |_, window, cx| {
24585            project.code_actions(&buffer, anchor..anchor, window, cx)
24586        })
24587        .unwrap();
24588
24589    fake_server
24590        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24591            Ok(Some(vec![
24592                lsp::CodeLens {
24593                    range: lsp::Range::default(),
24594                    command: Some(lsp::Command {
24595                        title: "Code lens command".to_owned(),
24596                        command: "_the/command".to_owned(),
24597                        arguments: None,
24598                    }),
24599                    data: None,
24600                },
24601                lsp::CodeLens {
24602                    range: lsp::Range::default(),
24603                    command: Some(lsp::Command {
24604                        title: "Command not in capabilities".to_owned(),
24605                        command: "not in capabilities".to_owned(),
24606                        arguments: None,
24607                    }),
24608                    data: None,
24609                },
24610                lsp::CodeLens {
24611                    range: lsp::Range {
24612                        start: lsp::Position {
24613                            line: 1,
24614                            character: 1,
24615                        },
24616                        end: lsp::Position {
24617                            line: 1,
24618                            character: 1,
24619                        },
24620                    },
24621                    command: Some(lsp::Command {
24622                        title: "Command not in range".to_owned(),
24623                        command: "_the/command".to_owned(),
24624                        arguments: None,
24625                    }),
24626                    data: None,
24627                },
24628            ]))
24629        })
24630        .next()
24631        .await;
24632
24633    let actions = actions.await.unwrap();
24634    assert_eq!(
24635        actions.len(),
24636        1,
24637        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24638    );
24639    let action = actions[0].clone();
24640    let apply = project.update(cx, |project, cx| {
24641        project.apply_code_action(buffer.clone(), action, true, cx)
24642    });
24643
24644    // Resolving the code action does not populate its edits. In absence of
24645    // edits, we must execute the given command.
24646    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24647        |mut lens, _| async move {
24648            let lens_command = lens.command.as_mut().expect("should have a command");
24649            assert_eq!(lens_command.title, "Code lens command");
24650            lens_command.arguments = Some(vec![json!("the-argument")]);
24651            Ok(lens)
24652        },
24653    );
24654
24655    // While executing the command, the language server sends the editor
24656    // a `workspaceEdit` request.
24657    fake_server
24658        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24659            let fake = fake_server.clone();
24660            move |params, _| {
24661                assert_eq!(params.command, "_the/command");
24662                let fake = fake.clone();
24663                async move {
24664                    fake.server
24665                        .request::<lsp::request::ApplyWorkspaceEdit>(
24666                            lsp::ApplyWorkspaceEditParams {
24667                                label: None,
24668                                edit: lsp::WorkspaceEdit {
24669                                    changes: Some(
24670                                        [(
24671                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24672                                            vec![lsp::TextEdit {
24673                                                range: lsp::Range::new(
24674                                                    lsp::Position::new(0, 0),
24675                                                    lsp::Position::new(0, 0),
24676                                                ),
24677                                                new_text: "X".into(),
24678                                            }],
24679                                        )]
24680                                        .into_iter()
24681                                        .collect(),
24682                                    ),
24683                                    ..lsp::WorkspaceEdit::default()
24684                                },
24685                            },
24686                        )
24687                        .await
24688                        .into_response()
24689                        .unwrap();
24690                    Ok(Some(json!(null)))
24691                }
24692            }
24693        })
24694        .next()
24695        .await;
24696
24697    // Applying the code lens command returns a project transaction containing the edits
24698    // sent by the language server in its `workspaceEdit` request.
24699    let transaction = apply.await.unwrap();
24700    assert!(transaction.0.contains_key(&buffer));
24701    buffer.update(cx, |buffer, cx| {
24702        assert_eq!(buffer.text(), "Xa");
24703        buffer.undo(cx);
24704        assert_eq!(buffer.text(), "a");
24705    });
24706
24707    let actions_after_edits = cx
24708        .update_window(*workspace, |_, window, cx| {
24709            project.code_actions(&buffer, anchor..anchor, window, cx)
24710        })
24711        .unwrap()
24712        .await
24713        .unwrap();
24714    assert_eq!(
24715        actions, actions_after_edits,
24716        "For the same selection, same code lens actions should be returned"
24717    );
24718
24719    let _responses =
24720        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24721            panic!("No more code lens requests are expected");
24722        });
24723    editor.update_in(cx, |editor, window, cx| {
24724        editor.select_all(&SelectAll, window, cx);
24725    });
24726    cx.executor().run_until_parked();
24727    let new_actions = cx
24728        .update_window(*workspace, |_, window, cx| {
24729            project.code_actions(&buffer, anchor..anchor, window, cx)
24730        })
24731        .unwrap()
24732        .await
24733        .unwrap();
24734    assert_eq!(
24735        actions, new_actions,
24736        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24737    );
24738}
24739
24740#[gpui::test]
24741async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24742    init_test(cx, |_| {});
24743
24744    let fs = FakeFs::new(cx.executor());
24745    let main_text = r#"fn main() {
24746println!("1");
24747println!("2");
24748println!("3");
24749println!("4");
24750println!("5");
24751}"#;
24752    let lib_text = "mod foo {}";
24753    fs.insert_tree(
24754        path!("/a"),
24755        json!({
24756            "lib.rs": lib_text,
24757            "main.rs": main_text,
24758        }),
24759    )
24760    .await;
24761
24762    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24763    let (workspace, cx) =
24764        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24765    let worktree_id = workspace.update(cx, |workspace, cx| {
24766        workspace.project().update(cx, |project, cx| {
24767            project.worktrees(cx).next().unwrap().read(cx).id()
24768        })
24769    });
24770
24771    let expected_ranges = vec![
24772        Point::new(0, 0)..Point::new(0, 0),
24773        Point::new(1, 0)..Point::new(1, 1),
24774        Point::new(2, 0)..Point::new(2, 2),
24775        Point::new(3, 0)..Point::new(3, 3),
24776    ];
24777
24778    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24779    let editor_1 = workspace
24780        .update_in(cx, |workspace, window, cx| {
24781            workspace.open_path(
24782                (worktree_id, rel_path("main.rs")),
24783                Some(pane_1.downgrade()),
24784                true,
24785                window,
24786                cx,
24787            )
24788        })
24789        .unwrap()
24790        .await
24791        .downcast::<Editor>()
24792        .unwrap();
24793    pane_1.update(cx, |pane, cx| {
24794        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24795        open_editor.update(cx, |editor, cx| {
24796            assert_eq!(
24797                editor.display_text(cx),
24798                main_text,
24799                "Original main.rs text on initial open",
24800            );
24801            assert_eq!(
24802                editor
24803                    .selections
24804                    .all::<Point>(&editor.display_snapshot(cx))
24805                    .into_iter()
24806                    .map(|s| s.range())
24807                    .collect::<Vec<_>>(),
24808                vec![Point::zero()..Point::zero()],
24809                "Default selections on initial open",
24810            );
24811        })
24812    });
24813    editor_1.update_in(cx, |editor, window, cx| {
24814        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24815            s.select_ranges(expected_ranges.clone());
24816        });
24817    });
24818
24819    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24820        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24821    });
24822    let editor_2 = workspace
24823        .update_in(cx, |workspace, window, cx| {
24824            workspace.open_path(
24825                (worktree_id, rel_path("main.rs")),
24826                Some(pane_2.downgrade()),
24827                true,
24828                window,
24829                cx,
24830            )
24831        })
24832        .unwrap()
24833        .await
24834        .downcast::<Editor>()
24835        .unwrap();
24836    pane_2.update(cx, |pane, cx| {
24837        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24838        open_editor.update(cx, |editor, cx| {
24839            assert_eq!(
24840                editor.display_text(cx),
24841                main_text,
24842                "Original main.rs text on initial open in another panel",
24843            );
24844            assert_eq!(
24845                editor
24846                    .selections
24847                    .all::<Point>(&editor.display_snapshot(cx))
24848                    .into_iter()
24849                    .map(|s| s.range())
24850                    .collect::<Vec<_>>(),
24851                vec![Point::zero()..Point::zero()],
24852                "Default selections on initial open in another panel",
24853            );
24854        })
24855    });
24856
24857    editor_2.update_in(cx, |editor, window, cx| {
24858        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24859    });
24860
24861    let _other_editor_1 = workspace
24862        .update_in(cx, |workspace, window, cx| {
24863            workspace.open_path(
24864                (worktree_id, rel_path("lib.rs")),
24865                Some(pane_1.downgrade()),
24866                true,
24867                window,
24868                cx,
24869            )
24870        })
24871        .unwrap()
24872        .await
24873        .downcast::<Editor>()
24874        .unwrap();
24875    pane_1
24876        .update_in(cx, |pane, window, cx| {
24877            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24878        })
24879        .await
24880        .unwrap();
24881    drop(editor_1);
24882    pane_1.update(cx, |pane, cx| {
24883        pane.active_item()
24884            .unwrap()
24885            .downcast::<Editor>()
24886            .unwrap()
24887            .update(cx, |editor, cx| {
24888                assert_eq!(
24889                    editor.display_text(cx),
24890                    lib_text,
24891                    "Other file should be open and active",
24892                );
24893            });
24894        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24895    });
24896
24897    let _other_editor_2 = workspace
24898        .update_in(cx, |workspace, window, cx| {
24899            workspace.open_path(
24900                (worktree_id, rel_path("lib.rs")),
24901                Some(pane_2.downgrade()),
24902                true,
24903                window,
24904                cx,
24905            )
24906        })
24907        .unwrap()
24908        .await
24909        .downcast::<Editor>()
24910        .unwrap();
24911    pane_2
24912        .update_in(cx, |pane, window, cx| {
24913            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24914        })
24915        .await
24916        .unwrap();
24917    drop(editor_2);
24918    pane_2.update(cx, |pane, cx| {
24919        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24920        open_editor.update(cx, |editor, cx| {
24921            assert_eq!(
24922                editor.display_text(cx),
24923                lib_text,
24924                "Other file should be open and active in another panel too",
24925            );
24926        });
24927        assert_eq!(
24928            pane.items().count(),
24929            1,
24930            "No other editors should be open in another pane",
24931        );
24932    });
24933
24934    let _editor_1_reopened = workspace
24935        .update_in(cx, |workspace, window, cx| {
24936            workspace.open_path(
24937                (worktree_id, rel_path("main.rs")),
24938                Some(pane_1.downgrade()),
24939                true,
24940                window,
24941                cx,
24942            )
24943        })
24944        .unwrap()
24945        .await
24946        .downcast::<Editor>()
24947        .unwrap();
24948    let _editor_2_reopened = workspace
24949        .update_in(cx, |workspace, window, cx| {
24950            workspace.open_path(
24951                (worktree_id, rel_path("main.rs")),
24952                Some(pane_2.downgrade()),
24953                true,
24954                window,
24955                cx,
24956            )
24957        })
24958        .unwrap()
24959        .await
24960        .downcast::<Editor>()
24961        .unwrap();
24962    pane_1.update(cx, |pane, cx| {
24963        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24964        open_editor.update(cx, |editor, cx| {
24965            assert_eq!(
24966                editor.display_text(cx),
24967                main_text,
24968                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24969            );
24970            assert_eq!(
24971                editor
24972                    .selections
24973                    .all::<Point>(&editor.display_snapshot(cx))
24974                    .into_iter()
24975                    .map(|s| s.range())
24976                    .collect::<Vec<_>>(),
24977                expected_ranges,
24978                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24979            );
24980        })
24981    });
24982    pane_2.update(cx, |pane, cx| {
24983        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24984        open_editor.update(cx, |editor, cx| {
24985            assert_eq!(
24986                editor.display_text(cx),
24987                r#"fn main() {
24988⋯rintln!("1");
24989⋯intln!("2");
24990⋯ntln!("3");
24991println!("4");
24992println!("5");
24993}"#,
24994                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24995            );
24996            assert_eq!(
24997                editor
24998                    .selections
24999                    .all::<Point>(&editor.display_snapshot(cx))
25000                    .into_iter()
25001                    .map(|s| s.range())
25002                    .collect::<Vec<_>>(),
25003                vec![Point::zero()..Point::zero()],
25004                "Previous editor in the 2nd pane had no selections changed hence should restore none",
25005            );
25006        })
25007    });
25008}
25009
25010#[gpui::test]
25011async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
25012    init_test(cx, |_| {});
25013
25014    let fs = FakeFs::new(cx.executor());
25015    let main_text = r#"fn main() {
25016println!("1");
25017println!("2");
25018println!("3");
25019println!("4");
25020println!("5");
25021}"#;
25022    let lib_text = "mod foo {}";
25023    fs.insert_tree(
25024        path!("/a"),
25025        json!({
25026            "lib.rs": lib_text,
25027            "main.rs": main_text,
25028        }),
25029    )
25030    .await;
25031
25032    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
25033    let (workspace, cx) =
25034        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25035    let worktree_id = workspace.update(cx, |workspace, cx| {
25036        workspace.project().update(cx, |project, cx| {
25037            project.worktrees(cx).next().unwrap().read(cx).id()
25038        })
25039    });
25040
25041    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25042    let editor = workspace
25043        .update_in(cx, |workspace, window, cx| {
25044            workspace.open_path(
25045                (worktree_id, rel_path("main.rs")),
25046                Some(pane.downgrade()),
25047                true,
25048                window,
25049                cx,
25050            )
25051        })
25052        .unwrap()
25053        .await
25054        .downcast::<Editor>()
25055        .unwrap();
25056    pane.update(cx, |pane, cx| {
25057        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25058        open_editor.update(cx, |editor, cx| {
25059            assert_eq!(
25060                editor.display_text(cx),
25061                main_text,
25062                "Original main.rs text on initial open",
25063            );
25064        })
25065    });
25066    editor.update_in(cx, |editor, window, cx| {
25067        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
25068    });
25069
25070    cx.update_global(|store: &mut SettingsStore, cx| {
25071        store.update_user_settings(cx, |s| {
25072            s.workspace.restore_on_file_reopen = Some(false);
25073        });
25074    });
25075    editor.update_in(cx, |editor, window, cx| {
25076        editor.fold_ranges(
25077            vec![
25078                Point::new(1, 0)..Point::new(1, 1),
25079                Point::new(2, 0)..Point::new(2, 2),
25080                Point::new(3, 0)..Point::new(3, 3),
25081            ],
25082            false,
25083            window,
25084            cx,
25085        );
25086    });
25087    pane.update_in(cx, |pane, window, cx| {
25088        pane.close_all_items(&CloseAllItems::default(), window, cx)
25089    })
25090    .await
25091    .unwrap();
25092    pane.update(cx, |pane, _| {
25093        assert!(pane.active_item().is_none());
25094    });
25095    cx.update_global(|store: &mut SettingsStore, cx| {
25096        store.update_user_settings(cx, |s| {
25097            s.workspace.restore_on_file_reopen = Some(true);
25098        });
25099    });
25100
25101    let _editor_reopened = workspace
25102        .update_in(cx, |workspace, window, cx| {
25103            workspace.open_path(
25104                (worktree_id, rel_path("main.rs")),
25105                Some(pane.downgrade()),
25106                true,
25107                window,
25108                cx,
25109            )
25110        })
25111        .unwrap()
25112        .await
25113        .downcast::<Editor>()
25114        .unwrap();
25115    pane.update(cx, |pane, cx| {
25116        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25117        open_editor.update(cx, |editor, cx| {
25118            assert_eq!(
25119                editor.display_text(cx),
25120                main_text,
25121                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
25122            );
25123        })
25124    });
25125}
25126
25127#[gpui::test]
25128async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
25129    struct EmptyModalView {
25130        focus_handle: gpui::FocusHandle,
25131    }
25132    impl EventEmitter<DismissEvent> for EmptyModalView {}
25133    impl Render for EmptyModalView {
25134        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
25135            div()
25136        }
25137    }
25138    impl Focusable for EmptyModalView {
25139        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
25140            self.focus_handle.clone()
25141        }
25142    }
25143    impl workspace::ModalView for EmptyModalView {}
25144    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
25145        EmptyModalView {
25146            focus_handle: cx.focus_handle(),
25147        }
25148    }
25149
25150    init_test(cx, |_| {});
25151
25152    let fs = FakeFs::new(cx.executor());
25153    let project = Project::test(fs, [], cx).await;
25154    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25155    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25156    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25157    let editor = cx.new_window_entity(|window, cx| {
25158        Editor::new(
25159            EditorMode::full(),
25160            buffer,
25161            Some(project.clone()),
25162            window,
25163            cx,
25164        )
25165    });
25166    workspace
25167        .update(cx, |workspace, window, cx| {
25168            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25169        })
25170        .unwrap();
25171    editor.update_in(cx, |editor, window, cx| {
25172        editor.open_context_menu(&OpenContextMenu, window, cx);
25173        assert!(editor.mouse_context_menu.is_some());
25174    });
25175    workspace
25176        .update(cx, |workspace, window, cx| {
25177            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25178        })
25179        .unwrap();
25180    cx.read(|cx| {
25181        assert!(editor.read(cx).mouse_context_menu.is_none());
25182    });
25183}
25184
25185fn set_linked_edit_ranges(
25186    opening: (Point, Point),
25187    closing: (Point, Point),
25188    editor: &mut Editor,
25189    cx: &mut Context<Editor>,
25190) {
25191    let Some((buffer, _)) = editor
25192        .buffer
25193        .read(cx)
25194        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25195    else {
25196        panic!("Failed to get buffer for selection position");
25197    };
25198    let buffer = buffer.read(cx);
25199    let buffer_id = buffer.remote_id();
25200    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25201    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25202    let mut linked_ranges = HashMap::default();
25203    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25204    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25205}
25206
25207#[gpui::test]
25208async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25209    init_test(cx, |_| {});
25210
25211    let fs = FakeFs::new(cx.executor());
25212    fs.insert_file(path!("/file.html"), Default::default())
25213        .await;
25214
25215    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25216
25217    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25218    let html_language = Arc::new(Language::new(
25219        LanguageConfig {
25220            name: "HTML".into(),
25221            matcher: LanguageMatcher {
25222                path_suffixes: vec!["html".to_string()],
25223                ..LanguageMatcher::default()
25224            },
25225            brackets: BracketPairConfig {
25226                pairs: vec![BracketPair {
25227                    start: "<".into(),
25228                    end: ">".into(),
25229                    close: true,
25230                    ..Default::default()
25231                }],
25232                ..Default::default()
25233            },
25234            ..Default::default()
25235        },
25236        Some(tree_sitter_html::LANGUAGE.into()),
25237    ));
25238    language_registry.add(html_language);
25239    let mut fake_servers = language_registry.register_fake_lsp(
25240        "HTML",
25241        FakeLspAdapter {
25242            capabilities: lsp::ServerCapabilities {
25243                completion_provider: Some(lsp::CompletionOptions {
25244                    resolve_provider: Some(true),
25245                    ..Default::default()
25246                }),
25247                ..Default::default()
25248            },
25249            ..Default::default()
25250        },
25251    );
25252
25253    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25254    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25255
25256    let worktree_id = workspace
25257        .update(cx, |workspace, _window, cx| {
25258            workspace.project().update(cx, |project, cx| {
25259                project.worktrees(cx).next().unwrap().read(cx).id()
25260            })
25261        })
25262        .unwrap();
25263    project
25264        .update(cx, |project, cx| {
25265            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25266        })
25267        .await
25268        .unwrap();
25269    let editor = workspace
25270        .update(cx, |workspace, window, cx| {
25271            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25272        })
25273        .unwrap()
25274        .await
25275        .unwrap()
25276        .downcast::<Editor>()
25277        .unwrap();
25278
25279    let fake_server = fake_servers.next().await.unwrap();
25280    editor.update_in(cx, |editor, window, cx| {
25281        editor.set_text("<ad></ad>", window, cx);
25282        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25283            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25284        });
25285        set_linked_edit_ranges(
25286            (Point::new(0, 1), Point::new(0, 3)),
25287            (Point::new(0, 6), Point::new(0, 8)),
25288            editor,
25289            cx,
25290        );
25291    });
25292    let mut completion_handle =
25293        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25294            Ok(Some(lsp::CompletionResponse::Array(vec![
25295                lsp::CompletionItem {
25296                    label: "head".to_string(),
25297                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25298                        lsp::InsertReplaceEdit {
25299                            new_text: "head".to_string(),
25300                            insert: lsp::Range::new(
25301                                lsp::Position::new(0, 1),
25302                                lsp::Position::new(0, 3),
25303                            ),
25304                            replace: lsp::Range::new(
25305                                lsp::Position::new(0, 1),
25306                                lsp::Position::new(0, 3),
25307                            ),
25308                        },
25309                    )),
25310                    ..Default::default()
25311                },
25312            ])))
25313        });
25314    editor.update_in(cx, |editor, window, cx| {
25315        editor.show_completions(&ShowCompletions, window, cx);
25316    });
25317    cx.run_until_parked();
25318    completion_handle.next().await.unwrap();
25319    editor.update(cx, |editor, _| {
25320        assert!(
25321            editor.context_menu_visible(),
25322            "Completion menu should be visible"
25323        );
25324    });
25325    editor.update_in(cx, |editor, window, cx| {
25326        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25327    });
25328    cx.executor().run_until_parked();
25329    editor.update(cx, |editor, cx| {
25330        assert_eq!(editor.text(cx), "<head></head>");
25331    });
25332}
25333
25334#[gpui::test]
25335async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25336    init_test(cx, |_| {});
25337
25338    let mut cx = EditorTestContext::new(cx).await;
25339    let language = Arc::new(Language::new(
25340        LanguageConfig {
25341            name: "TSX".into(),
25342            matcher: LanguageMatcher {
25343                path_suffixes: vec!["tsx".to_string()],
25344                ..LanguageMatcher::default()
25345            },
25346            brackets: BracketPairConfig {
25347                pairs: vec![BracketPair {
25348                    start: "<".into(),
25349                    end: ">".into(),
25350                    close: true,
25351                    ..Default::default()
25352                }],
25353                ..Default::default()
25354            },
25355            linked_edit_characters: HashSet::from_iter(['.']),
25356            ..Default::default()
25357        },
25358        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25359    ));
25360    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25361
25362    // Test typing > does not extend linked pair
25363    cx.set_state("<divˇ<div></div>");
25364    cx.update_editor(|editor, _, cx| {
25365        set_linked_edit_ranges(
25366            (Point::new(0, 1), Point::new(0, 4)),
25367            (Point::new(0, 11), Point::new(0, 14)),
25368            editor,
25369            cx,
25370        );
25371    });
25372    cx.update_editor(|editor, window, cx| {
25373        editor.handle_input(">", window, cx);
25374    });
25375    cx.assert_editor_state("<div>ˇ<div></div>");
25376
25377    // Test typing . do extend linked pair
25378    cx.set_state("<Animatedˇ></Animated>");
25379    cx.update_editor(|editor, _, cx| {
25380        set_linked_edit_ranges(
25381            (Point::new(0, 1), Point::new(0, 9)),
25382            (Point::new(0, 12), Point::new(0, 20)),
25383            editor,
25384            cx,
25385        );
25386    });
25387    cx.update_editor(|editor, window, cx| {
25388        editor.handle_input(".", window, cx);
25389    });
25390    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25391    cx.update_editor(|editor, _, cx| {
25392        set_linked_edit_ranges(
25393            (Point::new(0, 1), Point::new(0, 10)),
25394            (Point::new(0, 13), Point::new(0, 21)),
25395            editor,
25396            cx,
25397        );
25398    });
25399    cx.update_editor(|editor, window, cx| {
25400        editor.handle_input("V", window, cx);
25401    });
25402    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25403}
25404
25405#[gpui::test]
25406async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25407    init_test(cx, |_| {});
25408
25409    let fs = FakeFs::new(cx.executor());
25410    fs.insert_tree(
25411        path!("/root"),
25412        json!({
25413            "a": {
25414                "main.rs": "fn main() {}",
25415            },
25416            "foo": {
25417                "bar": {
25418                    "external_file.rs": "pub mod external {}",
25419                }
25420            }
25421        }),
25422    )
25423    .await;
25424
25425    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25426    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25427    language_registry.add(rust_lang());
25428    let _fake_servers = language_registry.register_fake_lsp(
25429        "Rust",
25430        FakeLspAdapter {
25431            ..FakeLspAdapter::default()
25432        },
25433    );
25434    let (workspace, cx) =
25435        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25436    let worktree_id = workspace.update(cx, |workspace, cx| {
25437        workspace.project().update(cx, |project, cx| {
25438            project.worktrees(cx).next().unwrap().read(cx).id()
25439        })
25440    });
25441
25442    let assert_language_servers_count =
25443        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25444            project.update(cx, |project, cx| {
25445                let current = project
25446                    .lsp_store()
25447                    .read(cx)
25448                    .as_local()
25449                    .unwrap()
25450                    .language_servers
25451                    .len();
25452                assert_eq!(expected, current, "{context}");
25453            });
25454        };
25455
25456    assert_language_servers_count(
25457        0,
25458        "No servers should be running before any file is open",
25459        cx,
25460    );
25461    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25462    let main_editor = workspace
25463        .update_in(cx, |workspace, window, cx| {
25464            workspace.open_path(
25465                (worktree_id, rel_path("main.rs")),
25466                Some(pane.downgrade()),
25467                true,
25468                window,
25469                cx,
25470            )
25471        })
25472        .unwrap()
25473        .await
25474        .downcast::<Editor>()
25475        .unwrap();
25476    pane.update(cx, |pane, cx| {
25477        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25478        open_editor.update(cx, |editor, cx| {
25479            assert_eq!(
25480                editor.display_text(cx),
25481                "fn main() {}",
25482                "Original main.rs text on initial open",
25483            );
25484        });
25485        assert_eq!(open_editor, main_editor);
25486    });
25487    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25488
25489    let external_editor = workspace
25490        .update_in(cx, |workspace, window, cx| {
25491            workspace.open_abs_path(
25492                PathBuf::from("/root/foo/bar/external_file.rs"),
25493                OpenOptions::default(),
25494                window,
25495                cx,
25496            )
25497        })
25498        .await
25499        .expect("opening external file")
25500        .downcast::<Editor>()
25501        .expect("downcasted external file's open element to editor");
25502    pane.update(cx, |pane, cx| {
25503        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25504        open_editor.update(cx, |editor, cx| {
25505            assert_eq!(
25506                editor.display_text(cx),
25507                "pub mod external {}",
25508                "External file is open now",
25509            );
25510        });
25511        assert_eq!(open_editor, external_editor);
25512    });
25513    assert_language_servers_count(
25514        1,
25515        "Second, external, *.rs file should join the existing server",
25516        cx,
25517    );
25518
25519    pane.update_in(cx, |pane, window, cx| {
25520        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25521    })
25522    .await
25523    .unwrap();
25524    pane.update_in(cx, |pane, window, cx| {
25525        pane.navigate_backward(&Default::default(), window, cx);
25526    });
25527    cx.run_until_parked();
25528    pane.update(cx, |pane, cx| {
25529        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25530        open_editor.update(cx, |editor, cx| {
25531            assert_eq!(
25532                editor.display_text(cx),
25533                "pub mod external {}",
25534                "External file is open now",
25535            );
25536        });
25537    });
25538    assert_language_servers_count(
25539        1,
25540        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25541        cx,
25542    );
25543
25544    cx.update(|_, cx| {
25545        workspace::reload(cx);
25546    });
25547    assert_language_servers_count(
25548        1,
25549        "After reloading the worktree with local and external files opened, only one project should be started",
25550        cx,
25551    );
25552}
25553
25554#[gpui::test]
25555async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25556    init_test(cx, |_| {});
25557
25558    let mut cx = EditorTestContext::new(cx).await;
25559    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25560    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25561
25562    // test cursor move to start of each line on tab
25563    // for `if`, `elif`, `else`, `while`, `with` and `for`
25564    cx.set_state(indoc! {"
25565        def main():
25566        ˇ    for item in items:
25567        ˇ        while item.active:
25568        ˇ            if item.value > 10:
25569        ˇ                continue
25570        ˇ            elif item.value < 0:
25571        ˇ                break
25572        ˇ            else:
25573        ˇ                with item.context() as ctx:
25574        ˇ                    yield count
25575        ˇ        else:
25576        ˇ            log('while else')
25577        ˇ    else:
25578        ˇ        log('for else')
25579    "});
25580    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25581    cx.assert_editor_state(indoc! {"
25582        def main():
25583            ˇfor item in items:
25584                ˇwhile item.active:
25585                    ˇif item.value > 10:
25586                        ˇcontinue
25587                    ˇelif item.value < 0:
25588                        ˇbreak
25589                    ˇelse:
25590                        ˇwith item.context() as ctx:
25591                            ˇyield count
25592                ˇelse:
25593                    ˇlog('while else')
25594            ˇelse:
25595                ˇlog('for else')
25596    "});
25597    // test relative indent is preserved when tab
25598    // for `if`, `elif`, `else`, `while`, `with` and `for`
25599    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25600    cx.assert_editor_state(indoc! {"
25601        def main():
25602                ˇfor item in items:
25603                    ˇwhile item.active:
25604                        ˇif item.value > 10:
25605                            ˇcontinue
25606                        ˇelif item.value < 0:
25607                            ˇbreak
25608                        ˇelse:
25609                            ˇwith item.context() as ctx:
25610                                ˇyield count
25611                    ˇelse:
25612                        ˇlog('while else')
25613                ˇelse:
25614                    ˇlog('for else')
25615    "});
25616
25617    // test cursor move to start of each line on tab
25618    // for `try`, `except`, `else`, `finally`, `match` and `def`
25619    cx.set_state(indoc! {"
25620        def main():
25621        ˇ    try:
25622        ˇ        fetch()
25623        ˇ    except ValueError:
25624        ˇ        handle_error()
25625        ˇ    else:
25626        ˇ        match value:
25627        ˇ            case _:
25628        ˇ    finally:
25629        ˇ        def status():
25630        ˇ            return 0
25631    "});
25632    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25633    cx.assert_editor_state(indoc! {"
25634        def main():
25635            ˇtry:
25636                ˇfetch()
25637            ˇexcept ValueError:
25638                ˇhandle_error()
25639            ˇelse:
25640                ˇmatch value:
25641                    ˇcase _:
25642            ˇfinally:
25643                ˇdef status():
25644                    ˇreturn 0
25645    "});
25646    // test relative indent is preserved when tab
25647    // for `try`, `except`, `else`, `finally`, `match` and `def`
25648    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25649    cx.assert_editor_state(indoc! {"
25650        def main():
25651                ˇtry:
25652                    ˇfetch()
25653                ˇexcept ValueError:
25654                    ˇhandle_error()
25655                ˇelse:
25656                    ˇmatch value:
25657                        ˇcase _:
25658                ˇfinally:
25659                    ˇdef status():
25660                        ˇreturn 0
25661    "});
25662}
25663
25664#[gpui::test]
25665async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25666    init_test(cx, |_| {});
25667
25668    let mut cx = EditorTestContext::new(cx).await;
25669    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25670    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25671
25672    // test `else` auto outdents when typed inside `if` block
25673    cx.set_state(indoc! {"
25674        def main():
25675            if i == 2:
25676                return
25677                ˇ
25678    "});
25679    cx.update_editor(|editor, window, cx| {
25680        editor.handle_input("else:", window, cx);
25681    });
25682    cx.assert_editor_state(indoc! {"
25683        def main():
25684            if i == 2:
25685                return
25686            else:ˇ
25687    "});
25688
25689    // test `except` auto outdents when typed inside `try` block
25690    cx.set_state(indoc! {"
25691        def main():
25692            try:
25693                i = 2
25694                ˇ
25695    "});
25696    cx.update_editor(|editor, window, cx| {
25697        editor.handle_input("except:", window, cx);
25698    });
25699    cx.assert_editor_state(indoc! {"
25700        def main():
25701            try:
25702                i = 2
25703            except:ˇ
25704    "});
25705
25706    // test `else` auto outdents when typed inside `except` block
25707    cx.set_state(indoc! {"
25708        def main():
25709            try:
25710                i = 2
25711            except:
25712                j = 2
25713                ˇ
25714    "});
25715    cx.update_editor(|editor, window, cx| {
25716        editor.handle_input("else:", window, cx);
25717    });
25718    cx.assert_editor_state(indoc! {"
25719        def main():
25720            try:
25721                i = 2
25722            except:
25723                j = 2
25724            else:ˇ
25725    "});
25726
25727    // test `finally` auto outdents when typed inside `else` block
25728    cx.set_state(indoc! {"
25729        def main():
25730            try:
25731                i = 2
25732            except:
25733                j = 2
25734            else:
25735                k = 2
25736                ˇ
25737    "});
25738    cx.update_editor(|editor, window, cx| {
25739        editor.handle_input("finally:", window, cx);
25740    });
25741    cx.assert_editor_state(indoc! {"
25742        def main():
25743            try:
25744                i = 2
25745            except:
25746                j = 2
25747            else:
25748                k = 2
25749            finally:ˇ
25750    "});
25751
25752    // test `else` does not outdents when typed inside `except` block right after for block
25753    cx.set_state(indoc! {"
25754        def main():
25755            try:
25756                i = 2
25757            except:
25758                for i in range(n):
25759                    pass
25760                ˇ
25761    "});
25762    cx.update_editor(|editor, window, cx| {
25763        editor.handle_input("else:", window, cx);
25764    });
25765    cx.assert_editor_state(indoc! {"
25766        def main():
25767            try:
25768                i = 2
25769            except:
25770                for i in range(n):
25771                    pass
25772                else:ˇ
25773    "});
25774
25775    // test `finally` auto outdents when typed inside `else` block right after for block
25776    cx.set_state(indoc! {"
25777        def main():
25778            try:
25779                i = 2
25780            except:
25781                j = 2
25782            else:
25783                for i in range(n):
25784                    pass
25785                ˇ
25786    "});
25787    cx.update_editor(|editor, window, cx| {
25788        editor.handle_input("finally:", window, cx);
25789    });
25790    cx.assert_editor_state(indoc! {"
25791        def main():
25792            try:
25793                i = 2
25794            except:
25795                j = 2
25796            else:
25797                for i in range(n):
25798                    pass
25799            finally:ˇ
25800    "});
25801
25802    // test `except` outdents to inner "try" block
25803    cx.set_state(indoc! {"
25804        def main():
25805            try:
25806                i = 2
25807                if i == 2:
25808                    try:
25809                        i = 3
25810                        ˇ
25811    "});
25812    cx.update_editor(|editor, window, cx| {
25813        editor.handle_input("except:", window, cx);
25814    });
25815    cx.assert_editor_state(indoc! {"
25816        def main():
25817            try:
25818                i = 2
25819                if i == 2:
25820                    try:
25821                        i = 3
25822                    except:ˇ
25823    "});
25824
25825    // test `except` outdents to outer "try" block
25826    cx.set_state(indoc! {"
25827        def main():
25828            try:
25829                i = 2
25830                if i == 2:
25831                    try:
25832                        i = 3
25833                ˇ
25834    "});
25835    cx.update_editor(|editor, window, cx| {
25836        editor.handle_input("except:", window, cx);
25837    });
25838    cx.assert_editor_state(indoc! {"
25839        def main():
25840            try:
25841                i = 2
25842                if i == 2:
25843                    try:
25844                        i = 3
25845            except:ˇ
25846    "});
25847
25848    // test `else` stays at correct indent when typed after `for` block
25849    cx.set_state(indoc! {"
25850        def main():
25851            for i in range(10):
25852                if i == 3:
25853                    break
25854            ˇ
25855    "});
25856    cx.update_editor(|editor, window, cx| {
25857        editor.handle_input("else:", window, cx);
25858    });
25859    cx.assert_editor_state(indoc! {"
25860        def main():
25861            for i in range(10):
25862                if i == 3:
25863                    break
25864            else:ˇ
25865    "});
25866
25867    // test does not outdent on typing after line with square brackets
25868    cx.set_state(indoc! {"
25869        def f() -> list[str]:
25870            ˇ
25871    "});
25872    cx.update_editor(|editor, window, cx| {
25873        editor.handle_input("a", window, cx);
25874    });
25875    cx.assert_editor_state(indoc! {"
25876        def f() -> list[str]:
2587725878    "});
25879
25880    // test does not outdent on typing : after case keyword
25881    cx.set_state(indoc! {"
25882        match 1:
25883            caseˇ
25884    "});
25885    cx.update_editor(|editor, window, cx| {
25886        editor.handle_input(":", window, cx);
25887    });
25888    cx.assert_editor_state(indoc! {"
25889        match 1:
25890            case:ˇ
25891    "});
25892}
25893
25894#[gpui::test]
25895async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25896    init_test(cx, |_| {});
25897    update_test_language_settings(cx, |settings| {
25898        settings.defaults.extend_comment_on_newline = Some(false);
25899    });
25900    let mut cx = EditorTestContext::new(cx).await;
25901    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25902    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25903
25904    // test correct indent after newline on comment
25905    cx.set_state(indoc! {"
25906        # COMMENT:ˇ
25907    "});
25908    cx.update_editor(|editor, window, cx| {
25909        editor.newline(&Newline, window, cx);
25910    });
25911    cx.assert_editor_state(indoc! {"
25912        # COMMENT:
25913        ˇ
25914    "});
25915
25916    // test correct indent after newline in brackets
25917    cx.set_state(indoc! {"
25918        {ˇ}
25919    "});
25920    cx.update_editor(|editor, window, cx| {
25921        editor.newline(&Newline, window, cx);
25922    });
25923    cx.run_until_parked();
25924    cx.assert_editor_state(indoc! {"
25925        {
25926            ˇ
25927        }
25928    "});
25929
25930    cx.set_state(indoc! {"
25931        (ˇ)
25932    "});
25933    cx.update_editor(|editor, window, cx| {
25934        editor.newline(&Newline, window, cx);
25935    });
25936    cx.run_until_parked();
25937    cx.assert_editor_state(indoc! {"
25938        (
25939            ˇ
25940        )
25941    "});
25942
25943    // do not indent after empty lists or dictionaries
25944    cx.set_state(indoc! {"
25945        a = []ˇ
25946    "});
25947    cx.update_editor(|editor, window, cx| {
25948        editor.newline(&Newline, window, cx);
25949    });
25950    cx.run_until_parked();
25951    cx.assert_editor_state(indoc! {"
25952        a = []
25953        ˇ
25954    "});
25955}
25956
25957#[gpui::test]
25958async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25959    init_test(cx, |_| {});
25960
25961    let mut cx = EditorTestContext::new(cx).await;
25962    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25963    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25964
25965    // test cursor move to start of each line on tab
25966    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25967    cx.set_state(indoc! {"
25968        function main() {
25969        ˇ    for item in $items; do
25970        ˇ        while [ -n \"$item\" ]; do
25971        ˇ            if [ \"$value\" -gt 10 ]; then
25972        ˇ                continue
25973        ˇ            elif [ \"$value\" -lt 0 ]; then
25974        ˇ                break
25975        ˇ            else
25976        ˇ                echo \"$item\"
25977        ˇ            fi
25978        ˇ        done
25979        ˇ    done
25980        ˇ}
25981    "});
25982    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25983    cx.assert_editor_state(indoc! {"
25984        function main() {
25985            ˇfor item in $items; do
25986                ˇwhile [ -n \"$item\" ]; do
25987                    ˇif [ \"$value\" -gt 10 ]; then
25988                        ˇcontinue
25989                    ˇelif [ \"$value\" -lt 0 ]; then
25990                        ˇbreak
25991                    ˇelse
25992                        ˇecho \"$item\"
25993                    ˇfi
25994                ˇdone
25995            ˇdone
25996        ˇ}
25997    "});
25998    // test relative indent is preserved when tab
25999    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26000    cx.assert_editor_state(indoc! {"
26001        function main() {
26002                ˇfor item in $items; do
26003                    ˇwhile [ -n \"$item\" ]; do
26004                        ˇif [ \"$value\" -gt 10 ]; then
26005                            ˇcontinue
26006                        ˇelif [ \"$value\" -lt 0 ]; then
26007                            ˇbreak
26008                        ˇelse
26009                            ˇecho \"$item\"
26010                        ˇfi
26011                    ˇdone
26012                ˇdone
26013            ˇ}
26014    "});
26015
26016    // test cursor move to start of each line on tab
26017    // for `case` statement with patterns
26018    cx.set_state(indoc! {"
26019        function handle() {
26020        ˇ    case \"$1\" in
26021        ˇ        start)
26022        ˇ            echo \"a\"
26023        ˇ            ;;
26024        ˇ        stop)
26025        ˇ            echo \"b\"
26026        ˇ            ;;
26027        ˇ        *)
26028        ˇ            echo \"c\"
26029        ˇ            ;;
26030        ˇ    esac
26031        ˇ}
26032    "});
26033    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
26034    cx.assert_editor_state(indoc! {"
26035        function handle() {
26036            ˇcase \"$1\" in
26037                ˇstart)
26038                    ˇecho \"a\"
26039                    ˇ;;
26040                ˇstop)
26041                    ˇecho \"b\"
26042                    ˇ;;
26043                ˇ*)
26044                    ˇecho \"c\"
26045                    ˇ;;
26046            ˇesac
26047        ˇ}
26048    "});
26049}
26050
26051#[gpui::test]
26052async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
26053    init_test(cx, |_| {});
26054
26055    let mut cx = EditorTestContext::new(cx).await;
26056    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26057    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26058
26059    // test indents on comment insert
26060    cx.set_state(indoc! {"
26061        function main() {
26062        ˇ    for item in $items; do
26063        ˇ        while [ -n \"$item\" ]; do
26064        ˇ            if [ \"$value\" -gt 10 ]; then
26065        ˇ                continue
26066        ˇ            elif [ \"$value\" -lt 0 ]; then
26067        ˇ                break
26068        ˇ            else
26069        ˇ                echo \"$item\"
26070        ˇ            fi
26071        ˇ        done
26072        ˇ    done
26073        ˇ}
26074    "});
26075    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
26076    cx.assert_editor_state(indoc! {"
26077        function main() {
26078        #ˇ    for item in $items; do
26079        #ˇ        while [ -n \"$item\" ]; do
26080        #ˇ            if [ \"$value\" -gt 10 ]; then
26081        #ˇ                continue
26082        #ˇ            elif [ \"$value\" -lt 0 ]; then
26083        #ˇ                break
26084        #ˇ            else
26085        #ˇ                echo \"$item\"
26086        #ˇ            fi
26087        #ˇ        done
26088        #ˇ    done
26089        #ˇ}
26090    "});
26091}
26092
26093#[gpui::test]
26094async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
26095    init_test(cx, |_| {});
26096
26097    let mut cx = EditorTestContext::new(cx).await;
26098    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26099    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26100
26101    // test `else` auto outdents when typed inside `if` block
26102    cx.set_state(indoc! {"
26103        if [ \"$1\" = \"test\" ]; then
26104            echo \"foo bar\"
26105            ˇ
26106    "});
26107    cx.update_editor(|editor, window, cx| {
26108        editor.handle_input("else", window, cx);
26109    });
26110    cx.assert_editor_state(indoc! {"
26111        if [ \"$1\" = \"test\" ]; then
26112            echo \"foo bar\"
26113        elseˇ
26114    "});
26115
26116    // test `elif` auto outdents when typed inside `if` block
26117    cx.set_state(indoc! {"
26118        if [ \"$1\" = \"test\" ]; then
26119            echo \"foo bar\"
26120            ˇ
26121    "});
26122    cx.update_editor(|editor, window, cx| {
26123        editor.handle_input("elif", window, cx);
26124    });
26125    cx.assert_editor_state(indoc! {"
26126        if [ \"$1\" = \"test\" ]; then
26127            echo \"foo bar\"
26128        elifˇ
26129    "});
26130
26131    // test `fi` auto outdents when typed inside `else` block
26132    cx.set_state(indoc! {"
26133        if [ \"$1\" = \"test\" ]; then
26134            echo \"foo bar\"
26135        else
26136            echo \"bar baz\"
26137            ˇ
26138    "});
26139    cx.update_editor(|editor, window, cx| {
26140        editor.handle_input("fi", window, cx);
26141    });
26142    cx.assert_editor_state(indoc! {"
26143        if [ \"$1\" = \"test\" ]; then
26144            echo \"foo bar\"
26145        else
26146            echo \"bar baz\"
26147        fiˇ
26148    "});
26149
26150    // test `done` auto outdents when typed inside `while` block
26151    cx.set_state(indoc! {"
26152        while read line; do
26153            echo \"$line\"
26154            ˇ
26155    "});
26156    cx.update_editor(|editor, window, cx| {
26157        editor.handle_input("done", window, cx);
26158    });
26159    cx.assert_editor_state(indoc! {"
26160        while read line; do
26161            echo \"$line\"
26162        doneˇ
26163    "});
26164
26165    // test `done` auto outdents when typed inside `for` block
26166    cx.set_state(indoc! {"
26167        for file in *.txt; do
26168            cat \"$file\"
26169            ˇ
26170    "});
26171    cx.update_editor(|editor, window, cx| {
26172        editor.handle_input("done", window, cx);
26173    });
26174    cx.assert_editor_state(indoc! {"
26175        for file in *.txt; do
26176            cat \"$file\"
26177        doneˇ
26178    "});
26179
26180    // test `esac` auto outdents when typed inside `case` block
26181    cx.set_state(indoc! {"
26182        case \"$1\" in
26183            start)
26184                echo \"foo bar\"
26185                ;;
26186            stop)
26187                echo \"bar baz\"
26188                ;;
26189            ˇ
26190    "});
26191    cx.update_editor(|editor, window, cx| {
26192        editor.handle_input("esac", window, cx);
26193    });
26194    cx.assert_editor_state(indoc! {"
26195        case \"$1\" in
26196            start)
26197                echo \"foo bar\"
26198                ;;
26199            stop)
26200                echo \"bar baz\"
26201                ;;
26202        esacˇ
26203    "});
26204
26205    // test `*)` auto outdents when typed inside `case` block
26206    cx.set_state(indoc! {"
26207        case \"$1\" in
26208            start)
26209                echo \"foo bar\"
26210                ;;
26211                ˇ
26212    "});
26213    cx.update_editor(|editor, window, cx| {
26214        editor.handle_input("*)", window, cx);
26215    });
26216    cx.assert_editor_state(indoc! {"
26217        case \"$1\" in
26218            start)
26219                echo \"foo bar\"
26220                ;;
26221            *)ˇ
26222    "});
26223
26224    // test `fi` outdents to correct level with nested if blocks
26225    cx.set_state(indoc! {"
26226        if [ \"$1\" = \"test\" ]; then
26227            echo \"outer if\"
26228            if [ \"$2\" = \"debug\" ]; then
26229                echo \"inner if\"
26230                ˇ
26231    "});
26232    cx.update_editor(|editor, window, cx| {
26233        editor.handle_input("fi", window, cx);
26234    });
26235    cx.assert_editor_state(indoc! {"
26236        if [ \"$1\" = \"test\" ]; then
26237            echo \"outer if\"
26238            if [ \"$2\" = \"debug\" ]; then
26239                echo \"inner if\"
26240            fiˇ
26241    "});
26242}
26243
26244#[gpui::test]
26245async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26246    init_test(cx, |_| {});
26247    update_test_language_settings(cx, |settings| {
26248        settings.defaults.extend_comment_on_newline = Some(false);
26249    });
26250    let mut cx = EditorTestContext::new(cx).await;
26251    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26252    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26253
26254    // test correct indent after newline on comment
26255    cx.set_state(indoc! {"
26256        # COMMENT:ˇ
26257    "});
26258    cx.update_editor(|editor, window, cx| {
26259        editor.newline(&Newline, window, cx);
26260    });
26261    cx.assert_editor_state(indoc! {"
26262        # COMMENT:
26263        ˇ
26264    "});
26265
26266    // test correct indent after newline after `then`
26267    cx.set_state(indoc! {"
26268
26269        if [ \"$1\" = \"test\" ]; thenˇ
26270    "});
26271    cx.update_editor(|editor, window, cx| {
26272        editor.newline(&Newline, window, cx);
26273    });
26274    cx.run_until_parked();
26275    cx.assert_editor_state(indoc! {"
26276
26277        if [ \"$1\" = \"test\" ]; then
26278            ˇ
26279    "});
26280
26281    // test correct indent after newline after `else`
26282    cx.set_state(indoc! {"
26283        if [ \"$1\" = \"test\" ]; then
26284        elseˇ
26285    "});
26286    cx.update_editor(|editor, window, cx| {
26287        editor.newline(&Newline, window, cx);
26288    });
26289    cx.run_until_parked();
26290    cx.assert_editor_state(indoc! {"
26291        if [ \"$1\" = \"test\" ]; then
26292        else
26293            ˇ
26294    "});
26295
26296    // test correct indent after newline after `elif`
26297    cx.set_state(indoc! {"
26298        if [ \"$1\" = \"test\" ]; then
26299        elifˇ
26300    "});
26301    cx.update_editor(|editor, window, cx| {
26302        editor.newline(&Newline, window, cx);
26303    });
26304    cx.run_until_parked();
26305    cx.assert_editor_state(indoc! {"
26306        if [ \"$1\" = \"test\" ]; then
26307        elif
26308            ˇ
26309    "});
26310
26311    // test correct indent after newline after `do`
26312    cx.set_state(indoc! {"
26313        for file in *.txt; doˇ
26314    "});
26315    cx.update_editor(|editor, window, cx| {
26316        editor.newline(&Newline, window, cx);
26317    });
26318    cx.run_until_parked();
26319    cx.assert_editor_state(indoc! {"
26320        for file in *.txt; do
26321            ˇ
26322    "});
26323
26324    // test correct indent after newline after case pattern
26325    cx.set_state(indoc! {"
26326        case \"$1\" in
26327            start)ˇ
26328    "});
26329    cx.update_editor(|editor, window, cx| {
26330        editor.newline(&Newline, window, cx);
26331    });
26332    cx.run_until_parked();
26333    cx.assert_editor_state(indoc! {"
26334        case \"$1\" in
26335            start)
26336                ˇ
26337    "});
26338
26339    // test correct indent after newline after case pattern
26340    cx.set_state(indoc! {"
26341        case \"$1\" in
26342            start)
26343                ;;
26344            *)ˇ
26345    "});
26346    cx.update_editor(|editor, window, cx| {
26347        editor.newline(&Newline, window, cx);
26348    });
26349    cx.run_until_parked();
26350    cx.assert_editor_state(indoc! {"
26351        case \"$1\" in
26352            start)
26353                ;;
26354            *)
26355                ˇ
26356    "});
26357
26358    // test correct indent after newline after function opening brace
26359    cx.set_state(indoc! {"
26360        function test() {ˇ}
26361    "});
26362    cx.update_editor(|editor, window, cx| {
26363        editor.newline(&Newline, window, cx);
26364    });
26365    cx.run_until_parked();
26366    cx.assert_editor_state(indoc! {"
26367        function test() {
26368            ˇ
26369        }
26370    "});
26371
26372    // test no extra indent after semicolon on same line
26373    cx.set_state(indoc! {"
26374        echo \"test\"26375    "});
26376    cx.update_editor(|editor, window, cx| {
26377        editor.newline(&Newline, window, cx);
26378    });
26379    cx.run_until_parked();
26380    cx.assert_editor_state(indoc! {"
26381        echo \"test\";
26382        ˇ
26383    "});
26384}
26385
26386fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26387    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26388    point..point
26389}
26390
26391#[track_caller]
26392fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26393    let (text, ranges) = marked_text_ranges(marked_text, true);
26394    assert_eq!(editor.text(cx), text);
26395    assert_eq!(
26396        editor.selections.ranges(&editor.display_snapshot(cx)),
26397        ranges
26398            .iter()
26399            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26400            .collect::<Vec<_>>(),
26401        "Assert selections are {}",
26402        marked_text
26403    );
26404}
26405
26406pub fn handle_signature_help_request(
26407    cx: &mut EditorLspTestContext,
26408    mocked_response: lsp::SignatureHelp,
26409) -> impl Future<Output = ()> + use<> {
26410    let mut request =
26411        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26412            let mocked_response = mocked_response.clone();
26413            async move { Ok(Some(mocked_response)) }
26414        });
26415
26416    async move {
26417        request.next().await;
26418    }
26419}
26420
26421#[track_caller]
26422pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26423    cx.update_editor(|editor, _, _| {
26424        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26425            let entries = menu.entries.borrow();
26426            let entries = entries
26427                .iter()
26428                .map(|entry| entry.string.as_str())
26429                .collect::<Vec<_>>();
26430            assert_eq!(entries, expected);
26431        } else {
26432            panic!("Expected completions menu");
26433        }
26434    });
26435}
26436
26437#[gpui::test]
26438async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26439    init_test(cx, |_| {});
26440    let mut cx = EditorLspTestContext::new_rust(
26441        lsp::ServerCapabilities {
26442            completion_provider: Some(lsp::CompletionOptions {
26443                ..Default::default()
26444            }),
26445            ..Default::default()
26446        },
26447        cx,
26448    )
26449    .await;
26450    cx.lsp
26451        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26452            Ok(Some(lsp::CompletionResponse::Array(vec![
26453                lsp::CompletionItem {
26454                    label: "unsafe".into(),
26455                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26456                        range: lsp::Range {
26457                            start: lsp::Position {
26458                                line: 0,
26459                                character: 9,
26460                            },
26461                            end: lsp::Position {
26462                                line: 0,
26463                                character: 11,
26464                            },
26465                        },
26466                        new_text: "unsafe".to_string(),
26467                    })),
26468                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26469                    ..Default::default()
26470                },
26471            ])))
26472        });
26473
26474    cx.update_editor(|editor, _, cx| {
26475        editor.project().unwrap().update(cx, |project, cx| {
26476            project.snippets().update(cx, |snippets, _cx| {
26477                snippets.add_snippet_for_test(
26478                    None,
26479                    PathBuf::from("test_snippets.json"),
26480                    vec![
26481                        Arc::new(project::snippet_provider::Snippet {
26482                            prefix: vec![
26483                                "unlimited word count".to_string(),
26484                                "unlimit word count".to_string(),
26485                                "unlimited unknown".to_string(),
26486                            ],
26487                            body: "this is many words".to_string(),
26488                            description: Some("description".to_string()),
26489                            name: "multi-word snippet test".to_string(),
26490                        }),
26491                        Arc::new(project::snippet_provider::Snippet {
26492                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26493                            body: "fewer words".to_string(),
26494                            description: Some("alt description".to_string()),
26495                            name: "other name".to_string(),
26496                        }),
26497                        Arc::new(project::snippet_provider::Snippet {
26498                            prefix: vec!["ab aa".to_string()],
26499                            body: "abcd".to_string(),
26500                            description: None,
26501                            name: "alphabet".to_string(),
26502                        }),
26503                    ],
26504                );
26505            });
26506        })
26507    });
26508
26509    let get_completions = |cx: &mut EditorLspTestContext| {
26510        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26511            Some(CodeContextMenu::Completions(context_menu)) => {
26512                let entries = context_menu.entries.borrow();
26513                entries
26514                    .iter()
26515                    .map(|entry| entry.string.clone())
26516                    .collect_vec()
26517            }
26518            _ => vec![],
26519        })
26520    };
26521
26522    // snippets:
26523    //  @foo
26524    //  foo bar
26525    //
26526    // when typing:
26527    //
26528    // when typing:
26529    //  - if I type a symbol "open the completions with snippets only"
26530    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26531    //
26532    // stuff we need:
26533    //  - filtering logic change?
26534    //  - remember how far back the completion started.
26535
26536    let test_cases: &[(&str, &[&str])] = &[
26537        (
26538            "un",
26539            &[
26540                "unsafe",
26541                "unlimit word count",
26542                "unlimited unknown",
26543                "unlimited word count",
26544                "unsnip",
26545            ],
26546        ),
26547        (
26548            "u ",
26549            &[
26550                "unlimit word count",
26551                "unlimited unknown",
26552                "unlimited word count",
26553            ],
26554        ),
26555        ("u a", &["ab aa", "unsafe"]), // unsAfe
26556        (
26557            "u u",
26558            &[
26559                "unsafe",
26560                "unlimit word count",
26561                "unlimited unknown", // ranked highest among snippets
26562                "unlimited word count",
26563                "unsnip",
26564            ],
26565        ),
26566        ("uw c", &["unlimit word count", "unlimited word count"]),
26567        (
26568            "u w",
26569            &[
26570                "unlimit word count",
26571                "unlimited word count",
26572                "unlimited unknown",
26573            ],
26574        ),
26575        ("u w ", &["unlimit word count", "unlimited word count"]),
26576        (
26577            "u ",
26578            &[
26579                "unlimit word count",
26580                "unlimited unknown",
26581                "unlimited word count",
26582            ],
26583        ),
26584        ("wor", &[]),
26585        ("uf", &["unsafe"]),
26586        ("af", &["unsafe"]),
26587        ("afu", &[]),
26588        (
26589            "ue",
26590            &["unsafe", "unlimited unknown", "unlimited word count"],
26591        ),
26592        ("@", &["@few"]),
26593        ("@few", &["@few"]),
26594        ("@ ", &[]),
26595        ("a@", &["@few"]),
26596        ("a@f", &["@few", "unsafe"]),
26597        ("a@fw", &["@few"]),
26598        ("a", &["ab aa", "unsafe"]),
26599        ("aa", &["ab aa"]),
26600        ("aaa", &["ab aa"]),
26601        ("ab", &["ab aa"]),
26602        ("ab ", &["ab aa"]),
26603        ("ab a", &["ab aa", "unsafe"]),
26604        ("ab ab", &["ab aa"]),
26605        ("ab ab aa", &["ab aa"]),
26606    ];
26607
26608    for &(input_to_simulate, expected_completions) in test_cases {
26609        cx.set_state("fn a() { ˇ }\n");
26610        for c in input_to_simulate.split("") {
26611            cx.simulate_input(c);
26612            cx.run_until_parked();
26613        }
26614        let expected_completions = expected_completions
26615            .iter()
26616            .map(|s| s.to_string())
26617            .collect_vec();
26618        assert_eq!(
26619            get_completions(&mut cx),
26620            expected_completions,
26621            "< actual / expected >, input = {input_to_simulate:?}",
26622        );
26623    }
26624}
26625
26626/// Handle completion request passing a marked string specifying where the completion
26627/// should be triggered from using '|' character, what range should be replaced, and what completions
26628/// should be returned using '<' and '>' to delimit the range.
26629///
26630/// Also see `handle_completion_request_with_insert_and_replace`.
26631#[track_caller]
26632pub fn handle_completion_request(
26633    marked_string: &str,
26634    completions: Vec<&'static str>,
26635    is_incomplete: bool,
26636    counter: Arc<AtomicUsize>,
26637    cx: &mut EditorLspTestContext,
26638) -> impl Future<Output = ()> {
26639    let complete_from_marker: TextRangeMarker = '|'.into();
26640    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26641    let (_, mut marked_ranges) = marked_text_ranges_by(
26642        marked_string,
26643        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26644    );
26645
26646    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26647        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26648    ));
26649    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26650    let replace_range =
26651        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26652
26653    let mut request =
26654        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26655            let completions = completions.clone();
26656            counter.fetch_add(1, atomic::Ordering::Release);
26657            async move {
26658                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26659                assert_eq!(
26660                    params.text_document_position.position,
26661                    complete_from_position
26662                );
26663                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26664                    is_incomplete,
26665                    item_defaults: None,
26666                    items: completions
26667                        .iter()
26668                        .map(|completion_text| lsp::CompletionItem {
26669                            label: completion_text.to_string(),
26670                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26671                                range: replace_range,
26672                                new_text: completion_text.to_string(),
26673                            })),
26674                            ..Default::default()
26675                        })
26676                        .collect(),
26677                })))
26678            }
26679        });
26680
26681    async move {
26682        request.next().await;
26683    }
26684}
26685
26686/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26687/// given instead, which also contains an `insert` range.
26688///
26689/// This function uses markers to define ranges:
26690/// - `|` marks the cursor position
26691/// - `<>` marks the replace range
26692/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26693pub fn handle_completion_request_with_insert_and_replace(
26694    cx: &mut EditorLspTestContext,
26695    marked_string: &str,
26696    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26697    counter: Arc<AtomicUsize>,
26698) -> impl Future<Output = ()> {
26699    let complete_from_marker: TextRangeMarker = '|'.into();
26700    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26701    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26702
26703    let (_, mut marked_ranges) = marked_text_ranges_by(
26704        marked_string,
26705        vec![
26706            complete_from_marker.clone(),
26707            replace_range_marker.clone(),
26708            insert_range_marker.clone(),
26709        ],
26710    );
26711
26712    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26713        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26714    ));
26715    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26716    let replace_range =
26717        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26718
26719    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26720        Some(ranges) if !ranges.is_empty() => {
26721            let range1 = ranges[0].clone();
26722            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26723        }
26724        _ => lsp::Range {
26725            start: replace_range.start,
26726            end: complete_from_position,
26727        },
26728    };
26729
26730    let mut request =
26731        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26732            let completions = completions.clone();
26733            counter.fetch_add(1, atomic::Ordering::Release);
26734            async move {
26735                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26736                assert_eq!(
26737                    params.text_document_position.position, complete_from_position,
26738                    "marker `|` position doesn't match",
26739                );
26740                Ok(Some(lsp::CompletionResponse::Array(
26741                    completions
26742                        .iter()
26743                        .map(|(label, new_text)| lsp::CompletionItem {
26744                            label: label.to_string(),
26745                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26746                                lsp::InsertReplaceEdit {
26747                                    insert: insert_range,
26748                                    replace: replace_range,
26749                                    new_text: new_text.to_string(),
26750                                },
26751                            )),
26752                            ..Default::default()
26753                        })
26754                        .collect(),
26755                )))
26756            }
26757        });
26758
26759    async move {
26760        request.next().await;
26761    }
26762}
26763
26764fn handle_resolve_completion_request(
26765    cx: &mut EditorLspTestContext,
26766    edits: Option<Vec<(&'static str, &'static str)>>,
26767) -> impl Future<Output = ()> {
26768    let edits = edits.map(|edits| {
26769        edits
26770            .iter()
26771            .map(|(marked_string, new_text)| {
26772                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26773                let replace_range = cx.to_lsp_range(
26774                    MultiBufferOffset(marked_ranges[0].start)
26775                        ..MultiBufferOffset(marked_ranges[0].end),
26776                );
26777                lsp::TextEdit::new(replace_range, new_text.to_string())
26778            })
26779            .collect::<Vec<_>>()
26780    });
26781
26782    let mut request =
26783        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26784            let edits = edits.clone();
26785            async move {
26786                Ok(lsp::CompletionItem {
26787                    additional_text_edits: edits,
26788                    ..Default::default()
26789                })
26790            }
26791        });
26792
26793    async move {
26794        request.next().await;
26795    }
26796}
26797
26798pub(crate) fn update_test_language_settings(
26799    cx: &mut TestAppContext,
26800    f: impl Fn(&mut AllLanguageSettingsContent),
26801) {
26802    cx.update(|cx| {
26803        SettingsStore::update_global(cx, |store, cx| {
26804            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26805        });
26806    });
26807}
26808
26809pub(crate) fn update_test_project_settings(
26810    cx: &mut TestAppContext,
26811    f: impl Fn(&mut ProjectSettingsContent),
26812) {
26813    cx.update(|cx| {
26814        SettingsStore::update_global(cx, |store, cx| {
26815            store.update_user_settings(cx, |settings| f(&mut settings.project));
26816        });
26817    });
26818}
26819
26820pub(crate) fn update_test_editor_settings(
26821    cx: &mut TestAppContext,
26822    f: impl Fn(&mut EditorSettingsContent),
26823) {
26824    cx.update(|cx| {
26825        SettingsStore::update_global(cx, |store, cx| {
26826            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26827        })
26828    })
26829}
26830
26831pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26832    cx.update(|cx| {
26833        assets::Assets.load_test_fonts(cx);
26834        let store = SettingsStore::test(cx);
26835        cx.set_global(store);
26836        theme::init(theme::LoadThemes::JustBase, cx);
26837        release_channel::init(semver::Version::new(0, 0, 0), cx);
26838        crate::init(cx);
26839    });
26840    zlog::init_test();
26841    update_test_language_settings(cx, f);
26842}
26843
26844#[track_caller]
26845fn assert_hunk_revert(
26846    not_reverted_text_with_selections: &str,
26847    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26848    expected_reverted_text_with_selections: &str,
26849    base_text: &str,
26850    cx: &mut EditorLspTestContext,
26851) {
26852    cx.set_state(not_reverted_text_with_selections);
26853    cx.set_head_text(base_text);
26854    cx.executor().run_until_parked();
26855
26856    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26857        let snapshot = editor.snapshot(window, cx);
26858        let reverted_hunk_statuses = snapshot
26859            .buffer_snapshot()
26860            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26861            .map(|hunk| hunk.status().kind)
26862            .collect::<Vec<_>>();
26863
26864        editor.git_restore(&Default::default(), window, cx);
26865        reverted_hunk_statuses
26866    });
26867    cx.executor().run_until_parked();
26868    cx.assert_editor_state(expected_reverted_text_with_selections);
26869    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26870}
26871
26872#[gpui::test(iterations = 10)]
26873async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26874    init_test(cx, |_| {});
26875
26876    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26877    let counter = diagnostic_requests.clone();
26878
26879    let fs = FakeFs::new(cx.executor());
26880    fs.insert_tree(
26881        path!("/a"),
26882        json!({
26883            "first.rs": "fn main() { let a = 5; }",
26884            "second.rs": "// Test file",
26885        }),
26886    )
26887    .await;
26888
26889    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26890    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26891    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26892
26893    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26894    language_registry.add(rust_lang());
26895    let mut fake_servers = language_registry.register_fake_lsp(
26896        "Rust",
26897        FakeLspAdapter {
26898            capabilities: lsp::ServerCapabilities {
26899                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26900                    lsp::DiagnosticOptions {
26901                        identifier: None,
26902                        inter_file_dependencies: true,
26903                        workspace_diagnostics: true,
26904                        work_done_progress_options: Default::default(),
26905                    },
26906                )),
26907                ..Default::default()
26908            },
26909            ..Default::default()
26910        },
26911    );
26912
26913    let editor = workspace
26914        .update(cx, |workspace, window, cx| {
26915            workspace.open_abs_path(
26916                PathBuf::from(path!("/a/first.rs")),
26917                OpenOptions::default(),
26918                window,
26919                cx,
26920            )
26921        })
26922        .unwrap()
26923        .await
26924        .unwrap()
26925        .downcast::<Editor>()
26926        .unwrap();
26927    let fake_server = fake_servers.next().await.unwrap();
26928    let server_id = fake_server.server.server_id();
26929    let mut first_request = fake_server
26930        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26931            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26932            let result_id = Some(new_result_id.to_string());
26933            assert_eq!(
26934                params.text_document.uri,
26935                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26936            );
26937            async move {
26938                Ok(lsp::DocumentDiagnosticReportResult::Report(
26939                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26940                        related_documents: None,
26941                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26942                            items: Vec::new(),
26943                            result_id,
26944                        },
26945                    }),
26946                ))
26947            }
26948        });
26949
26950    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
26951        project.update(cx, |project, cx| {
26952            let buffer_id = editor
26953                .read(cx)
26954                .buffer()
26955                .read(cx)
26956                .as_singleton()
26957                .expect("created a singleton buffer")
26958                .read(cx)
26959                .remote_id();
26960            let buffer_result_id = project
26961                .lsp_store()
26962                .read(cx)
26963                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
26964            assert_eq!(expected, buffer_result_id);
26965        });
26966    };
26967
26968    ensure_result_id(None, cx);
26969    cx.executor().advance_clock(Duration::from_millis(60));
26970    cx.executor().run_until_parked();
26971    assert_eq!(
26972        diagnostic_requests.load(atomic::Ordering::Acquire),
26973        1,
26974        "Opening file should trigger diagnostic request"
26975    );
26976    first_request
26977        .next()
26978        .await
26979        .expect("should have sent the first diagnostics pull request");
26980    ensure_result_id(Some(SharedString::new("1")), cx);
26981
26982    // Editing should trigger diagnostics
26983    editor.update_in(cx, |editor, window, cx| {
26984        editor.handle_input("2", window, cx)
26985    });
26986    cx.executor().advance_clock(Duration::from_millis(60));
26987    cx.executor().run_until_parked();
26988    assert_eq!(
26989        diagnostic_requests.load(atomic::Ordering::Acquire),
26990        2,
26991        "Editing should trigger diagnostic request"
26992    );
26993    ensure_result_id(Some(SharedString::new("2")), cx);
26994
26995    // Moving cursor should not trigger diagnostic request
26996    editor.update_in(cx, |editor, window, cx| {
26997        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26998            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26999        });
27000    });
27001    cx.executor().advance_clock(Duration::from_millis(60));
27002    cx.executor().run_until_parked();
27003    assert_eq!(
27004        diagnostic_requests.load(atomic::Ordering::Acquire),
27005        2,
27006        "Cursor movement should not trigger diagnostic request"
27007    );
27008    ensure_result_id(Some(SharedString::new("2")), cx);
27009    // Multiple rapid edits should be debounced
27010    for _ in 0..5 {
27011        editor.update_in(cx, |editor, window, cx| {
27012            editor.handle_input("x", window, cx)
27013        });
27014    }
27015    cx.executor().advance_clock(Duration::from_millis(60));
27016    cx.executor().run_until_parked();
27017
27018    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
27019    assert!(
27020        final_requests <= 4,
27021        "Multiple rapid edits should be debounced (got {final_requests} requests)",
27022    );
27023    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
27024}
27025
27026#[gpui::test]
27027async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
27028    // Regression test for issue #11671
27029    // Previously, adding a cursor after moving multiple cursors would reset
27030    // the cursor count instead of adding to the existing cursors.
27031    init_test(cx, |_| {});
27032    let mut cx = EditorTestContext::new(cx).await;
27033
27034    // Create a simple buffer with cursor at start
27035    cx.set_state(indoc! {"
27036        ˇaaaa
27037        bbbb
27038        cccc
27039        dddd
27040        eeee
27041        ffff
27042        gggg
27043        hhhh"});
27044
27045    // Add 2 cursors below (so we have 3 total)
27046    cx.update_editor(|editor, window, cx| {
27047        editor.add_selection_below(&Default::default(), window, cx);
27048        editor.add_selection_below(&Default::default(), window, cx);
27049    });
27050
27051    // Verify we have 3 cursors
27052    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
27053    assert_eq!(
27054        initial_count, 3,
27055        "Should have 3 cursors after adding 2 below"
27056    );
27057
27058    // Move down one line
27059    cx.update_editor(|editor, window, cx| {
27060        editor.move_down(&MoveDown, window, cx);
27061    });
27062
27063    // Add another cursor below
27064    cx.update_editor(|editor, window, cx| {
27065        editor.add_selection_below(&Default::default(), window, cx);
27066    });
27067
27068    // Should now have 4 cursors (3 original + 1 new)
27069    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
27070    assert_eq!(
27071        final_count, 4,
27072        "Should have 4 cursors after moving and adding another"
27073    );
27074}
27075
27076#[gpui::test]
27077async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
27078    init_test(cx, |_| {});
27079
27080    let mut cx = EditorTestContext::new(cx).await;
27081
27082    cx.set_state(indoc!(
27083        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
27084           Second line here"#
27085    ));
27086
27087    cx.update_editor(|editor, window, cx| {
27088        // Enable soft wrapping with a narrow width to force soft wrapping and
27089        // confirm that more than 2 rows are being displayed.
27090        editor.set_wrap_width(Some(100.0.into()), cx);
27091        assert!(editor.display_text(cx).lines().count() > 2);
27092
27093        editor.add_selection_below(
27094            &AddSelectionBelow {
27095                skip_soft_wrap: true,
27096            },
27097            window,
27098            cx,
27099        );
27100
27101        assert_eq!(
27102            display_ranges(editor, cx),
27103            &[
27104                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27105                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
27106            ]
27107        );
27108
27109        editor.add_selection_above(
27110            &AddSelectionAbove {
27111                skip_soft_wrap: true,
27112            },
27113            window,
27114            cx,
27115        );
27116
27117        assert_eq!(
27118            display_ranges(editor, cx),
27119            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27120        );
27121
27122        editor.add_selection_below(
27123            &AddSelectionBelow {
27124                skip_soft_wrap: false,
27125            },
27126            window,
27127            cx,
27128        );
27129
27130        assert_eq!(
27131            display_ranges(editor, cx),
27132            &[
27133                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
27134                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
27135            ]
27136        );
27137
27138        editor.add_selection_above(
27139            &AddSelectionAbove {
27140                skip_soft_wrap: false,
27141            },
27142            window,
27143            cx,
27144        );
27145
27146        assert_eq!(
27147            display_ranges(editor, cx),
27148            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27149        );
27150    });
27151}
27152
27153#[gpui::test]
27154async fn test_insert_snippet(cx: &mut TestAppContext) {
27155    init_test(cx, |_| {});
27156    let mut cx = EditorTestContext::new(cx).await;
27157
27158    cx.update_editor(|editor, _, cx| {
27159        editor.project().unwrap().update(cx, |project, cx| {
27160            project.snippets().update(cx, |snippets, _cx| {
27161                let snippet = project::snippet_provider::Snippet {
27162                    prefix: vec![], // no prefix needed!
27163                    body: "an Unspecified".to_string(),
27164                    description: Some("shhhh it's a secret".to_string()),
27165                    name: "super secret snippet".to_string(),
27166                };
27167                snippets.add_snippet_for_test(
27168                    None,
27169                    PathBuf::from("test_snippets.json"),
27170                    vec![Arc::new(snippet)],
27171                );
27172
27173                let snippet = project::snippet_provider::Snippet {
27174                    prefix: vec![], // no prefix needed!
27175                    body: " Location".to_string(),
27176                    description: Some("the word 'location'".to_string()),
27177                    name: "location word".to_string(),
27178                };
27179                snippets.add_snippet_for_test(
27180                    Some("Markdown".to_string()),
27181                    PathBuf::from("test_snippets.json"),
27182                    vec![Arc::new(snippet)],
27183                );
27184            });
27185        })
27186    });
27187
27188    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27189
27190    cx.update_editor(|editor, window, cx| {
27191        editor.insert_snippet_at_selections(
27192            &InsertSnippet {
27193                language: None,
27194                name: Some("super secret snippet".to_string()),
27195                snippet: None,
27196            },
27197            window,
27198            cx,
27199        );
27200
27201        // Language is specified in the action,
27202        // so the buffer language does not need to match
27203        editor.insert_snippet_at_selections(
27204            &InsertSnippet {
27205                language: Some("Markdown".to_string()),
27206                name: Some("location word".to_string()),
27207                snippet: None,
27208            },
27209            window,
27210            cx,
27211        );
27212
27213        editor.insert_snippet_at_selections(
27214            &InsertSnippet {
27215                language: None,
27216                name: None,
27217                snippet: Some("$0 after".to_string()),
27218            },
27219            window,
27220            cx,
27221        );
27222    });
27223
27224    cx.assert_editor_state(
27225        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27226    );
27227}
27228
27229#[gpui::test(iterations = 10)]
27230async fn test_document_colors(cx: &mut TestAppContext) {
27231    let expected_color = Rgba {
27232        r: 0.33,
27233        g: 0.33,
27234        b: 0.33,
27235        a: 0.33,
27236    };
27237
27238    init_test(cx, |_| {});
27239
27240    let fs = FakeFs::new(cx.executor());
27241    fs.insert_tree(
27242        path!("/a"),
27243        json!({
27244            "first.rs": "fn main() { let a = 5; }",
27245        }),
27246    )
27247    .await;
27248
27249    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27250    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27251    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27252
27253    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27254    language_registry.add(rust_lang());
27255    let mut fake_servers = language_registry.register_fake_lsp(
27256        "Rust",
27257        FakeLspAdapter {
27258            capabilities: lsp::ServerCapabilities {
27259                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27260                ..lsp::ServerCapabilities::default()
27261            },
27262            name: "rust-analyzer",
27263            ..FakeLspAdapter::default()
27264        },
27265    );
27266    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27267        "Rust",
27268        FakeLspAdapter {
27269            capabilities: lsp::ServerCapabilities {
27270                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27271                ..lsp::ServerCapabilities::default()
27272            },
27273            name: "not-rust-analyzer",
27274            ..FakeLspAdapter::default()
27275        },
27276    );
27277
27278    let editor = workspace
27279        .update(cx, |workspace, window, cx| {
27280            workspace.open_abs_path(
27281                PathBuf::from(path!("/a/first.rs")),
27282                OpenOptions::default(),
27283                window,
27284                cx,
27285            )
27286        })
27287        .unwrap()
27288        .await
27289        .unwrap()
27290        .downcast::<Editor>()
27291        .unwrap();
27292    let fake_language_server = fake_servers.next().await.unwrap();
27293    let fake_language_server_without_capabilities =
27294        fake_servers_without_capabilities.next().await.unwrap();
27295    let requests_made = Arc::new(AtomicUsize::new(0));
27296    let closure_requests_made = Arc::clone(&requests_made);
27297    let mut color_request_handle = fake_language_server
27298        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27299            let requests_made = Arc::clone(&closure_requests_made);
27300            async move {
27301                assert_eq!(
27302                    params.text_document.uri,
27303                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27304                );
27305                requests_made.fetch_add(1, atomic::Ordering::Release);
27306                Ok(vec![
27307                    lsp::ColorInformation {
27308                        range: lsp::Range {
27309                            start: lsp::Position {
27310                                line: 0,
27311                                character: 0,
27312                            },
27313                            end: lsp::Position {
27314                                line: 0,
27315                                character: 1,
27316                            },
27317                        },
27318                        color: lsp::Color {
27319                            red: 0.33,
27320                            green: 0.33,
27321                            blue: 0.33,
27322                            alpha: 0.33,
27323                        },
27324                    },
27325                    lsp::ColorInformation {
27326                        range: lsp::Range {
27327                            start: lsp::Position {
27328                                line: 0,
27329                                character: 0,
27330                            },
27331                            end: lsp::Position {
27332                                line: 0,
27333                                character: 1,
27334                            },
27335                        },
27336                        color: lsp::Color {
27337                            red: 0.33,
27338                            green: 0.33,
27339                            blue: 0.33,
27340                            alpha: 0.33,
27341                        },
27342                    },
27343                ])
27344            }
27345        });
27346
27347    let _handle = fake_language_server_without_capabilities
27348        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27349            panic!("Should not be called");
27350        });
27351    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27352    color_request_handle.next().await.unwrap();
27353    cx.run_until_parked();
27354    assert_eq!(
27355        1,
27356        requests_made.load(atomic::Ordering::Acquire),
27357        "Should query for colors once per editor open"
27358    );
27359    editor.update_in(cx, |editor, _, cx| {
27360        assert_eq!(
27361            vec![expected_color],
27362            extract_color_inlays(editor, cx),
27363            "Should have an initial inlay"
27364        );
27365    });
27366
27367    // opening another file in a split should not influence the LSP query counter
27368    workspace
27369        .update(cx, |workspace, window, cx| {
27370            assert_eq!(
27371                workspace.panes().len(),
27372                1,
27373                "Should have one pane with one editor"
27374            );
27375            workspace.move_item_to_pane_in_direction(
27376                &MoveItemToPaneInDirection {
27377                    direction: SplitDirection::Right,
27378                    focus: false,
27379                    clone: true,
27380                },
27381                window,
27382                cx,
27383            );
27384        })
27385        .unwrap();
27386    cx.run_until_parked();
27387    workspace
27388        .update(cx, |workspace, _, cx| {
27389            let panes = workspace.panes();
27390            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27391            for pane in panes {
27392                let editor = pane
27393                    .read(cx)
27394                    .active_item()
27395                    .and_then(|item| item.downcast::<Editor>())
27396                    .expect("Should have opened an editor in each split");
27397                let editor_file = editor
27398                    .read(cx)
27399                    .buffer()
27400                    .read(cx)
27401                    .as_singleton()
27402                    .expect("test deals with singleton buffers")
27403                    .read(cx)
27404                    .file()
27405                    .expect("test buffese should have a file")
27406                    .path();
27407                assert_eq!(
27408                    editor_file.as_ref(),
27409                    rel_path("first.rs"),
27410                    "Both editors should be opened for the same file"
27411                )
27412            }
27413        })
27414        .unwrap();
27415
27416    cx.executor().advance_clock(Duration::from_millis(500));
27417    let save = editor.update_in(cx, |editor, window, cx| {
27418        editor.move_to_end(&MoveToEnd, window, cx);
27419        editor.handle_input("dirty", window, cx);
27420        editor.save(
27421            SaveOptions {
27422                format: true,
27423                autosave: true,
27424            },
27425            project.clone(),
27426            window,
27427            cx,
27428        )
27429    });
27430    save.await.unwrap();
27431
27432    color_request_handle.next().await.unwrap();
27433    cx.run_until_parked();
27434    assert_eq!(
27435        2,
27436        requests_made.load(atomic::Ordering::Acquire),
27437        "Should query for colors once per save (deduplicated) and once per formatting after save"
27438    );
27439
27440    drop(editor);
27441    let close = workspace
27442        .update(cx, |workspace, window, cx| {
27443            workspace.active_pane().update(cx, |pane, cx| {
27444                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27445            })
27446        })
27447        .unwrap();
27448    close.await.unwrap();
27449    let close = workspace
27450        .update(cx, |workspace, window, cx| {
27451            workspace.active_pane().update(cx, |pane, cx| {
27452                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27453            })
27454        })
27455        .unwrap();
27456    close.await.unwrap();
27457    assert_eq!(
27458        2,
27459        requests_made.load(atomic::Ordering::Acquire),
27460        "After saving and closing all editors, no extra requests should be made"
27461    );
27462    workspace
27463        .update(cx, |workspace, _, cx| {
27464            assert!(
27465                workspace.active_item(cx).is_none(),
27466                "Should close all editors"
27467            )
27468        })
27469        .unwrap();
27470
27471    workspace
27472        .update(cx, |workspace, window, cx| {
27473            workspace.active_pane().update(cx, |pane, cx| {
27474                pane.navigate_backward(&workspace::GoBack, window, cx);
27475            })
27476        })
27477        .unwrap();
27478    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27479    cx.run_until_parked();
27480    let editor = workspace
27481        .update(cx, |workspace, _, cx| {
27482            workspace
27483                .active_item(cx)
27484                .expect("Should have reopened the editor again after navigating back")
27485                .downcast::<Editor>()
27486                .expect("Should be an editor")
27487        })
27488        .unwrap();
27489
27490    assert_eq!(
27491        2,
27492        requests_made.load(atomic::Ordering::Acquire),
27493        "Cache should be reused on buffer close and reopen"
27494    );
27495    editor.update(cx, |editor, cx| {
27496        assert_eq!(
27497            vec![expected_color],
27498            extract_color_inlays(editor, cx),
27499            "Should have an initial inlay"
27500        );
27501    });
27502
27503    drop(color_request_handle);
27504    let closure_requests_made = Arc::clone(&requests_made);
27505    let mut empty_color_request_handle = fake_language_server
27506        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27507            let requests_made = Arc::clone(&closure_requests_made);
27508            async move {
27509                assert_eq!(
27510                    params.text_document.uri,
27511                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27512                );
27513                requests_made.fetch_add(1, atomic::Ordering::Release);
27514                Ok(Vec::new())
27515            }
27516        });
27517    let save = editor.update_in(cx, |editor, window, cx| {
27518        editor.move_to_end(&MoveToEnd, window, cx);
27519        editor.handle_input("dirty_again", window, cx);
27520        editor.save(
27521            SaveOptions {
27522                format: false,
27523                autosave: true,
27524            },
27525            project.clone(),
27526            window,
27527            cx,
27528        )
27529    });
27530    save.await.unwrap();
27531
27532    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27533    empty_color_request_handle.next().await.unwrap();
27534    cx.run_until_parked();
27535    assert_eq!(
27536        3,
27537        requests_made.load(atomic::Ordering::Acquire),
27538        "Should query for colors once per save only, as formatting was not requested"
27539    );
27540    editor.update(cx, |editor, cx| {
27541        assert_eq!(
27542            Vec::<Rgba>::new(),
27543            extract_color_inlays(editor, cx),
27544            "Should clear all colors when the server returns an empty response"
27545        );
27546    });
27547}
27548
27549#[gpui::test]
27550async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27551    init_test(cx, |_| {});
27552    let (editor, cx) = cx.add_window_view(Editor::single_line);
27553    editor.update_in(cx, |editor, window, cx| {
27554        editor.set_text("oops\n\nwow\n", window, cx)
27555    });
27556    cx.run_until_parked();
27557    editor.update(cx, |editor, cx| {
27558        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27559    });
27560    editor.update(cx, |editor, cx| {
27561        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27562    });
27563    cx.run_until_parked();
27564    editor.update(cx, |editor, cx| {
27565        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27566    });
27567}
27568
27569#[gpui::test]
27570async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27571    init_test(cx, |_| {});
27572
27573    cx.update(|cx| {
27574        register_project_item::<Editor>(cx);
27575    });
27576
27577    let fs = FakeFs::new(cx.executor());
27578    fs.insert_tree("/root1", json!({})).await;
27579    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27580        .await;
27581
27582    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27583    let (workspace, cx) =
27584        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27585
27586    let worktree_id = project.update(cx, |project, cx| {
27587        project.worktrees(cx).next().unwrap().read(cx).id()
27588    });
27589
27590    let handle = workspace
27591        .update_in(cx, |workspace, window, cx| {
27592            let project_path = (worktree_id, rel_path("one.pdf"));
27593            workspace.open_path(project_path, None, true, window, cx)
27594        })
27595        .await
27596        .unwrap();
27597
27598    assert_eq!(
27599        handle.to_any_view().entity_type(),
27600        TypeId::of::<InvalidItemView>()
27601    );
27602}
27603
27604#[gpui::test]
27605async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27606    init_test(cx, |_| {});
27607
27608    let language = Arc::new(Language::new(
27609        LanguageConfig::default(),
27610        Some(tree_sitter_rust::LANGUAGE.into()),
27611    ));
27612
27613    // Test hierarchical sibling navigation
27614    let text = r#"
27615        fn outer() {
27616            if condition {
27617                let a = 1;
27618            }
27619            let b = 2;
27620        }
27621
27622        fn another() {
27623            let c = 3;
27624        }
27625    "#;
27626
27627    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27628    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27629    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27630
27631    // Wait for parsing to complete
27632    editor
27633        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27634        .await;
27635
27636    editor.update_in(cx, |editor, window, cx| {
27637        // Start by selecting "let a = 1;" inside the if block
27638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27639            s.select_display_ranges([
27640                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27641            ]);
27642        });
27643
27644        let initial_selection = editor
27645            .selections
27646            .display_ranges(&editor.display_snapshot(cx));
27647        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27648
27649        // Test select next sibling - should move up levels to find the next sibling
27650        // Since "let a = 1;" has no siblings in the if block, it should move up
27651        // to find "let b = 2;" which is a sibling of the if block
27652        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27653        let next_selection = editor
27654            .selections
27655            .display_ranges(&editor.display_snapshot(cx));
27656
27657        // Should have a selection and it should be different from the initial
27658        assert_eq!(
27659            next_selection.len(),
27660            1,
27661            "Should have one selection after next"
27662        );
27663        assert_ne!(
27664            next_selection[0], initial_selection[0],
27665            "Next sibling selection should be different"
27666        );
27667
27668        // Test hierarchical navigation by going to the end of the current function
27669        // and trying to navigate to the next function
27670        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27671            s.select_display_ranges([
27672                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27673            ]);
27674        });
27675
27676        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27677        let function_next_selection = editor
27678            .selections
27679            .display_ranges(&editor.display_snapshot(cx));
27680
27681        // Should move to the next function
27682        assert_eq!(
27683            function_next_selection.len(),
27684            1,
27685            "Should have one selection after function next"
27686        );
27687
27688        // Test select previous sibling navigation
27689        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27690        let prev_selection = editor
27691            .selections
27692            .display_ranges(&editor.display_snapshot(cx));
27693
27694        // Should have a selection and it should be different
27695        assert_eq!(
27696            prev_selection.len(),
27697            1,
27698            "Should have one selection after prev"
27699        );
27700        assert_ne!(
27701            prev_selection[0], function_next_selection[0],
27702            "Previous sibling selection should be different from next"
27703        );
27704    });
27705}
27706
27707#[gpui::test]
27708async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27709    init_test(cx, |_| {});
27710
27711    let mut cx = EditorTestContext::new(cx).await;
27712    cx.set_state(
27713        "let ˇvariable = 42;
27714let another = variable + 1;
27715let result = variable * 2;",
27716    );
27717
27718    // Set up document highlights manually (simulating LSP response)
27719    cx.update_editor(|editor, _window, cx| {
27720        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27721
27722        // Create highlights for "variable" occurrences
27723        let highlight_ranges = [
27724            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27725            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27726            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27727        ];
27728
27729        let anchor_ranges: Vec<_> = highlight_ranges
27730            .iter()
27731            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27732            .collect();
27733
27734        editor.highlight_background::<DocumentHighlightRead>(
27735            &anchor_ranges,
27736            |_, theme| theme.colors().editor_document_highlight_read_background,
27737            cx,
27738        );
27739    });
27740
27741    // Go to next highlight - should move to second "variable"
27742    cx.update_editor(|editor, window, cx| {
27743        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27744    });
27745    cx.assert_editor_state(
27746        "let variable = 42;
27747let another = ˇvariable + 1;
27748let result = variable * 2;",
27749    );
27750
27751    // Go to next highlight - should move to third "variable"
27752    cx.update_editor(|editor, window, cx| {
27753        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27754    });
27755    cx.assert_editor_state(
27756        "let variable = 42;
27757let another = variable + 1;
27758let result = ˇvariable * 2;",
27759    );
27760
27761    // Go to next highlight - should stay at third "variable" (no wrap-around)
27762    cx.update_editor(|editor, window, cx| {
27763        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27764    });
27765    cx.assert_editor_state(
27766        "let variable = 42;
27767let another = variable + 1;
27768let result = ˇvariable * 2;",
27769    );
27770
27771    // Now test going backwards from third position
27772    cx.update_editor(|editor, window, cx| {
27773        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27774    });
27775    cx.assert_editor_state(
27776        "let variable = 42;
27777let another = ˇvariable + 1;
27778let result = variable * 2;",
27779    );
27780
27781    // Go to previous highlight - should move to first "variable"
27782    cx.update_editor(|editor, window, cx| {
27783        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27784    });
27785    cx.assert_editor_state(
27786        "let ˇvariable = 42;
27787let another = variable + 1;
27788let result = variable * 2;",
27789    );
27790
27791    // Go to previous highlight - should stay on first "variable"
27792    cx.update_editor(|editor, window, cx| {
27793        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27794    });
27795    cx.assert_editor_state(
27796        "let ˇvariable = 42;
27797let another = variable + 1;
27798let result = variable * 2;",
27799    );
27800}
27801
27802#[gpui::test]
27803async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27804    cx: &mut gpui::TestAppContext,
27805) {
27806    init_test(cx, |_| {});
27807
27808    let url = "https://zed.dev";
27809
27810    let markdown_language = Arc::new(Language::new(
27811        LanguageConfig {
27812            name: "Markdown".into(),
27813            ..LanguageConfig::default()
27814        },
27815        None,
27816    ));
27817
27818    let mut cx = EditorTestContext::new(cx).await;
27819    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27820    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27821
27822    cx.update_editor(|editor, window, cx| {
27823        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27824        editor.paste(&Paste, window, cx);
27825    });
27826
27827    cx.assert_editor_state(&format!(
27828        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27829    ));
27830}
27831
27832#[gpui::test]
27833async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
27834    init_test(cx, |_| {});
27835
27836    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
27837    let mut cx = EditorTestContext::new(cx).await;
27838
27839    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27840
27841    // Case 1: Test if adding a character with multi cursors preserves nested list indents
27842    cx.set_state(&indoc! {"
27843        - [ ] Item 1
27844            - [ ] Item 1.a
27845        - [ˇ] Item 2
27846            - [ˇ] Item 2.a
27847            - [ˇ] Item 2.b
27848        "
27849    });
27850    cx.update_editor(|editor, window, cx| {
27851        editor.handle_input("x", window, cx);
27852    });
27853    cx.run_until_parked();
27854    cx.assert_editor_state(indoc! {"
27855        - [ ] Item 1
27856            - [ ] Item 1.a
27857        - [xˇ] Item 2
27858            - [xˇ] Item 2.a
27859            - [xˇ] Item 2.b
27860        "
27861    });
27862
27863    // Case 2: Test adding new line after nested list preserves indent of previous line
27864    cx.set_state(&indoc! {"
27865        - [ ] Item 1
27866            - [ ] Item 1.a
27867        - [x] Item 2
27868            - [x] Item 2.a
27869            - [x] Item 2.bˇ"
27870    });
27871    cx.update_editor(|editor, window, cx| {
27872        editor.newline(&Newline, window, cx);
27873    });
27874    cx.assert_editor_state(indoc! {"
27875        - [ ] Item 1
27876            - [ ] Item 1.a
27877        - [x] Item 2
27878            - [x] Item 2.a
27879            - [x] Item 2.b
27880            ˇ"
27881    });
27882
27883    // Case 3: Test adding a new nested list item preserves indent
27884    cx.set_state(&indoc! {"
27885        - [ ] Item 1
27886            - [ ] Item 1.a
27887        - [x] Item 2
27888            - [x] Item 2.a
27889            - [x] Item 2.b
27890            ˇ"
27891    });
27892    cx.update_editor(|editor, window, cx| {
27893        editor.handle_input("-", window, cx);
27894    });
27895    cx.run_until_parked();
27896    cx.assert_editor_state(indoc! {"
27897        - [ ] Item 1
27898            - [ ] Item 1.a
27899        - [x] Item 2
27900            - [x] Item 2.a
27901            - [x] Item 2.b
27902"
27903    });
27904    cx.update_editor(|editor, window, cx| {
27905        editor.handle_input(" [x] Item 2.c", window, cx);
27906    });
27907    cx.run_until_parked();
27908    cx.assert_editor_state(indoc! {"
27909        - [ ] Item 1
27910            - [ ] Item 1.a
27911        - [x] Item 2
27912            - [x] Item 2.a
27913            - [x] Item 2.b
27914            - [x] Item 2.cˇ"
27915    });
27916
27917    // Case 4: Test adding new line after nested ordered list preserves indent of previous line
27918    cx.set_state(indoc! {"
27919        1. Item 1
27920            1. Item 1.a
27921        2. Item 2
27922            1. Item 2.a
27923            2. Item 2.bˇ"
27924    });
27925    cx.update_editor(|editor, window, cx| {
27926        editor.newline(&Newline, window, cx);
27927    });
27928    cx.assert_editor_state(indoc! {"
27929        1. Item 1
27930            1. Item 1.a
27931        2. Item 2
27932            1. Item 2.a
27933            2. Item 2.b
27934            ˇ"
27935    });
27936
27937    // Case 5: Adding new ordered list item preserves indent
27938    cx.set_state(indoc! {"
27939        1. Item 1
27940            1. Item 1.a
27941        2. Item 2
27942            1. Item 2.a
27943            2. Item 2.b
27944            ˇ"
27945    });
27946    cx.update_editor(|editor, window, cx| {
27947        editor.handle_input("3", window, cx);
27948    });
27949    cx.run_until_parked();
27950    cx.assert_editor_state(indoc! {"
27951        1. Item 1
27952            1. Item 1.a
27953        2. Item 2
27954            1. Item 2.a
27955            2. Item 2.b
27956"
27957    });
27958    cx.update_editor(|editor, window, cx| {
27959        editor.handle_input(".", window, cx);
27960    });
27961    cx.run_until_parked();
27962    cx.assert_editor_state(indoc! {"
27963        1. Item 1
27964            1. Item 1.a
27965        2. Item 2
27966            1. Item 2.a
27967            2. Item 2.b
27968            3.ˇ"
27969    });
27970    cx.update_editor(|editor, window, cx| {
27971        editor.handle_input(" Item 2.c", window, cx);
27972    });
27973    cx.run_until_parked();
27974    cx.assert_editor_state(indoc! {"
27975        1. Item 1
27976            1. Item 1.a
27977        2. Item 2
27978            1. Item 2.a
27979            2. Item 2.b
27980            3. Item 2.cˇ"
27981    });
27982
27983    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
27984    cx.set_state(indoc! {"
27985        - Item 1
27986            - Item 1.a
27987            - Item 1.a
27988        ˇ"});
27989    cx.update_editor(|editor, window, cx| {
27990        editor.handle_input("-", window, cx);
27991    });
27992    cx.run_until_parked();
27993    cx.assert_editor_state(indoc! {"
27994        - Item 1
27995            - Item 1.a
27996            - Item 1.a
27997"});
27998
27999    // Case 7: Test blockquote newline preserves something
28000    cx.set_state(indoc! {"
28001        > Item 1ˇ"
28002    });
28003    cx.update_editor(|editor, window, cx| {
28004        editor.newline(&Newline, window, cx);
28005    });
28006    cx.assert_editor_state(indoc! {"
28007        > Item 1
28008        ˇ"
28009    });
28010}
28011
28012#[gpui::test]
28013async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
28014    cx: &mut gpui::TestAppContext,
28015) {
28016    init_test(cx, |_| {});
28017
28018    let url = "https://zed.dev";
28019
28020    let markdown_language = Arc::new(Language::new(
28021        LanguageConfig {
28022            name: "Markdown".into(),
28023            ..LanguageConfig::default()
28024        },
28025        None,
28026    ));
28027
28028    let mut cx = EditorTestContext::new(cx).await;
28029    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28030    cx.set_state(&format!(
28031        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
28032    ));
28033
28034    cx.update_editor(|editor, window, cx| {
28035        editor.copy(&Copy, window, cx);
28036    });
28037
28038    cx.set_state(&format!(
28039        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
28040    ));
28041
28042    cx.update_editor(|editor, window, cx| {
28043        editor.paste(&Paste, window, cx);
28044    });
28045
28046    cx.assert_editor_state(&format!(
28047        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
28048    ));
28049}
28050
28051#[gpui::test]
28052async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
28053    cx: &mut gpui::TestAppContext,
28054) {
28055    init_test(cx, |_| {});
28056
28057    let url = "https://zed.dev";
28058
28059    let markdown_language = Arc::new(Language::new(
28060        LanguageConfig {
28061            name: "Markdown".into(),
28062            ..LanguageConfig::default()
28063        },
28064        None,
28065    ));
28066
28067    let mut cx = EditorTestContext::new(cx).await;
28068    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28069    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
28070
28071    cx.update_editor(|editor, window, cx| {
28072        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28073        editor.paste(&Paste, window, cx);
28074    });
28075
28076    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
28077}
28078
28079#[gpui::test]
28080async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
28081    cx: &mut gpui::TestAppContext,
28082) {
28083    init_test(cx, |_| {});
28084
28085    let text = "Awesome";
28086
28087    let markdown_language = Arc::new(Language::new(
28088        LanguageConfig {
28089            name: "Markdown".into(),
28090            ..LanguageConfig::default()
28091        },
28092        None,
28093    ));
28094
28095    let mut cx = EditorTestContext::new(cx).await;
28096    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28097    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
28098
28099    cx.update_editor(|editor, window, cx| {
28100        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
28101        editor.paste(&Paste, window, cx);
28102    });
28103
28104    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
28105}
28106
28107#[gpui::test]
28108async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
28109    cx: &mut gpui::TestAppContext,
28110) {
28111    init_test(cx, |_| {});
28112
28113    let url = "https://zed.dev";
28114
28115    let markdown_language = Arc::new(Language::new(
28116        LanguageConfig {
28117            name: "Rust".into(),
28118            ..LanguageConfig::default()
28119        },
28120        None,
28121    ));
28122
28123    let mut cx = EditorTestContext::new(cx).await;
28124    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
28125    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
28126
28127    cx.update_editor(|editor, window, cx| {
28128        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28129        editor.paste(&Paste, window, cx);
28130    });
28131
28132    cx.assert_editor_state(&format!(
28133        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
28134    ));
28135}
28136
28137#[gpui::test]
28138async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
28139    cx: &mut TestAppContext,
28140) {
28141    init_test(cx, |_| {});
28142
28143    let url = "https://zed.dev";
28144
28145    let markdown_language = Arc::new(Language::new(
28146        LanguageConfig {
28147            name: "Markdown".into(),
28148            ..LanguageConfig::default()
28149        },
28150        None,
28151    ));
28152
28153    let (editor, cx) = cx.add_window_view(|window, cx| {
28154        let multi_buffer = MultiBuffer::build_multi(
28155            [
28156                ("this will embed -> link", vec![Point::row_range(0..1)]),
28157                ("this will replace -> link", vec![Point::row_range(0..1)]),
28158            ],
28159            cx,
28160        );
28161        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28162        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28163            s.select_ranges(vec![
28164                Point::new(0, 19)..Point::new(0, 23),
28165                Point::new(1, 21)..Point::new(1, 25),
28166            ])
28167        });
28168        let first_buffer_id = multi_buffer
28169            .read(cx)
28170            .excerpt_buffer_ids()
28171            .into_iter()
28172            .next()
28173            .unwrap();
28174        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28175        first_buffer.update(cx, |buffer, cx| {
28176            buffer.set_language(Some(markdown_language.clone()), cx);
28177        });
28178
28179        editor
28180    });
28181    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28182
28183    cx.update_editor(|editor, window, cx| {
28184        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28185        editor.paste(&Paste, window, cx);
28186    });
28187
28188    cx.assert_editor_state(&format!(
28189        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28190    ));
28191}
28192
28193#[gpui::test]
28194async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28195    init_test(cx, |_| {});
28196
28197    let fs = FakeFs::new(cx.executor());
28198    fs.insert_tree(
28199        path!("/project"),
28200        json!({
28201            "first.rs": "# First Document\nSome content here.",
28202            "second.rs": "Plain text content for second file.",
28203        }),
28204    )
28205    .await;
28206
28207    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28208    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28209    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28210
28211    let language = rust_lang();
28212    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28213    language_registry.add(language.clone());
28214    let mut fake_servers = language_registry.register_fake_lsp(
28215        "Rust",
28216        FakeLspAdapter {
28217            ..FakeLspAdapter::default()
28218        },
28219    );
28220
28221    let buffer1 = project
28222        .update(cx, |project, cx| {
28223            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28224        })
28225        .await
28226        .unwrap();
28227    let buffer2 = project
28228        .update(cx, |project, cx| {
28229            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28230        })
28231        .await
28232        .unwrap();
28233
28234    let multi_buffer = cx.new(|cx| {
28235        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28236        multi_buffer.set_excerpts_for_path(
28237            PathKey::for_buffer(&buffer1, cx),
28238            buffer1.clone(),
28239            [Point::zero()..buffer1.read(cx).max_point()],
28240            3,
28241            cx,
28242        );
28243        multi_buffer.set_excerpts_for_path(
28244            PathKey::for_buffer(&buffer2, cx),
28245            buffer2.clone(),
28246            [Point::zero()..buffer1.read(cx).max_point()],
28247            3,
28248            cx,
28249        );
28250        multi_buffer
28251    });
28252
28253    let (editor, cx) = cx.add_window_view(|window, cx| {
28254        Editor::new(
28255            EditorMode::full(),
28256            multi_buffer,
28257            Some(project.clone()),
28258            window,
28259            cx,
28260        )
28261    });
28262
28263    let fake_language_server = fake_servers.next().await.unwrap();
28264
28265    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28266
28267    let save = editor.update_in(cx, |editor, window, cx| {
28268        assert!(editor.is_dirty(cx));
28269
28270        editor.save(
28271            SaveOptions {
28272                format: true,
28273                autosave: true,
28274            },
28275            project,
28276            window,
28277            cx,
28278        )
28279    });
28280    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28281    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28282    let mut done_edit_rx = Some(done_edit_rx);
28283    let mut start_edit_tx = Some(start_edit_tx);
28284
28285    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28286        start_edit_tx.take().unwrap().send(()).unwrap();
28287        let done_edit_rx = done_edit_rx.take().unwrap();
28288        async move {
28289            done_edit_rx.await.unwrap();
28290            Ok(None)
28291        }
28292    });
28293
28294    start_edit_rx.await.unwrap();
28295    buffer2
28296        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28297        .unwrap();
28298
28299    done_edit_tx.send(()).unwrap();
28300
28301    save.await.unwrap();
28302    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28303}
28304
28305#[track_caller]
28306fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28307    editor
28308        .all_inlays(cx)
28309        .into_iter()
28310        .filter_map(|inlay| inlay.get_color())
28311        .map(Rgba::from)
28312        .collect()
28313}
28314
28315#[gpui::test]
28316fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28317    init_test(cx, |_| {});
28318
28319    let editor = cx.add_window(|window, cx| {
28320        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28321        build_editor(buffer, window, cx)
28322    });
28323
28324    editor
28325        .update(cx, |editor, window, cx| {
28326            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28327                s.select_display_ranges([
28328                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28329                ])
28330            });
28331
28332            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28333
28334            assert_eq!(
28335                editor.display_text(cx),
28336                "line1\nline2\nline2",
28337                "Duplicating last line upward should create duplicate above, not on same line"
28338            );
28339
28340            assert_eq!(
28341                editor
28342                    .selections
28343                    .display_ranges(&editor.display_snapshot(cx)),
28344                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28345                "Selection should move to the duplicated line"
28346            );
28347        })
28348        .unwrap();
28349}
28350
28351#[gpui::test]
28352async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28353    init_test(cx, |_| {});
28354
28355    let mut cx = EditorTestContext::new(cx).await;
28356
28357    cx.set_state("line1\nline2ˇ");
28358
28359    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28360
28361    let clipboard_text = cx
28362        .read_from_clipboard()
28363        .and_then(|item| item.text().as_deref().map(str::to_string));
28364
28365    assert_eq!(
28366        clipboard_text,
28367        Some("line2\n".to_string()),
28368        "Copying a line without trailing newline should include a newline"
28369    );
28370
28371    cx.set_state("line1\nˇ");
28372
28373    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28374
28375    cx.assert_editor_state("line1\nline2\nˇ");
28376}
28377
28378#[gpui::test]
28379async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28380    init_test(cx, |_| {});
28381
28382    let mut cx = EditorTestContext::new(cx).await;
28383
28384    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28385
28386    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28387
28388    let clipboard_text = cx
28389        .read_from_clipboard()
28390        .and_then(|item| item.text().as_deref().map(str::to_string));
28391
28392    assert_eq!(
28393        clipboard_text,
28394        Some("line1\nline2\nline3\n".to_string()),
28395        "Copying multiple lines should include a single newline between lines"
28396    );
28397
28398    cx.set_state("lineA\nˇ");
28399
28400    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28401
28402    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28403}
28404
28405#[gpui::test]
28406async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28407    init_test(cx, |_| {});
28408
28409    let mut cx = EditorTestContext::new(cx).await;
28410
28411    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28412
28413    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28414
28415    let clipboard_text = cx
28416        .read_from_clipboard()
28417        .and_then(|item| item.text().as_deref().map(str::to_string));
28418
28419    assert_eq!(
28420        clipboard_text,
28421        Some("line1\nline2\nline3\n".to_string()),
28422        "Copying multiple lines should include a single newline between lines"
28423    );
28424
28425    cx.set_state("lineA\nˇ");
28426
28427    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28428
28429    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28430}
28431
28432#[gpui::test]
28433async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28434    init_test(cx, |_| {});
28435
28436    let mut cx = EditorTestContext::new(cx).await;
28437
28438    cx.set_state("line1\nline2ˇ");
28439    cx.update_editor(|e, window, cx| {
28440        e.set_mode(EditorMode::SingleLine);
28441        assert!(e.key_context(window, cx).contains("end_of_input"));
28442    });
28443    cx.set_state("ˇline1\nline2");
28444    cx.update_editor(|e, window, cx| {
28445        assert!(!e.key_context(window, cx).contains("end_of_input"));
28446    });
28447    cx.set_state("line1ˇ\nline2");
28448    cx.update_editor(|e, window, cx| {
28449        assert!(!e.key_context(window, cx).contains("end_of_input"));
28450    });
28451}
28452
28453#[gpui::test]
28454async fn test_sticky_scroll(cx: &mut TestAppContext) {
28455    init_test(cx, |_| {});
28456    let mut cx = EditorTestContext::new(cx).await;
28457
28458    let buffer = indoc! {"
28459            ˇfn foo() {
28460                let abc = 123;
28461            }
28462            struct Bar;
28463            impl Bar {
28464                fn new() -> Self {
28465                    Self
28466                }
28467            }
28468            fn baz() {
28469            }
28470        "};
28471    cx.set_state(&buffer);
28472
28473    cx.update_editor(|e, _, cx| {
28474        e.buffer()
28475            .read(cx)
28476            .as_singleton()
28477            .unwrap()
28478            .update(cx, |buffer, cx| {
28479                buffer.set_language(Some(rust_lang()), cx);
28480            })
28481    });
28482
28483    let mut sticky_headers = |offset: ScrollOffset| {
28484        cx.update_editor(|e, window, cx| {
28485            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28486            let style = e.style(cx).clone();
28487            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28488                .into_iter()
28489                .map(
28490                    |StickyHeader {
28491                         start_point,
28492                         offset,
28493                         ..
28494                     }| { (start_point, offset) },
28495                )
28496                .collect::<Vec<_>>()
28497        })
28498    };
28499
28500    let fn_foo = Point { row: 0, column: 0 };
28501    let impl_bar = Point { row: 4, column: 0 };
28502    let fn_new = Point { row: 5, column: 4 };
28503
28504    assert_eq!(sticky_headers(0.0), vec![]);
28505    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28506    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28507    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28508    assert_eq!(sticky_headers(2.0), vec![]);
28509    assert_eq!(sticky_headers(2.5), vec![]);
28510    assert_eq!(sticky_headers(3.0), vec![]);
28511    assert_eq!(sticky_headers(3.5), vec![]);
28512    assert_eq!(sticky_headers(4.0), vec![]);
28513    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28514    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28515    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28516    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28517    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28518    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28519    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28520    assert_eq!(sticky_headers(8.0), vec![]);
28521    assert_eq!(sticky_headers(8.5), vec![]);
28522    assert_eq!(sticky_headers(9.0), vec![]);
28523    assert_eq!(sticky_headers(9.5), vec![]);
28524    assert_eq!(sticky_headers(10.0), vec![]);
28525}
28526
28527#[gpui::test]
28528async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28529    init_test(cx, |_| {});
28530    cx.update(|cx| {
28531        SettingsStore::update_global(cx, |store, cx| {
28532            store.update_user_settings(cx, |settings| {
28533                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28534                    enabled: Some(true),
28535                })
28536            });
28537        });
28538    });
28539    let mut cx = EditorTestContext::new(cx).await;
28540
28541    let line_height = cx.update_editor(|editor, window, cx| {
28542        editor
28543            .style(cx)
28544            .text
28545            .line_height_in_pixels(window.rem_size())
28546    });
28547
28548    let buffer = indoc! {"
28549            ˇfn foo() {
28550                let abc = 123;
28551            }
28552            struct Bar;
28553            impl Bar {
28554                fn new() -> Self {
28555                    Self
28556                }
28557            }
28558            fn baz() {
28559            }
28560        "};
28561    cx.set_state(&buffer);
28562
28563    cx.update_editor(|e, _, cx| {
28564        e.buffer()
28565            .read(cx)
28566            .as_singleton()
28567            .unwrap()
28568            .update(cx, |buffer, cx| {
28569                buffer.set_language(Some(rust_lang()), cx);
28570            })
28571    });
28572
28573    let fn_foo = || empty_range(0, 0);
28574    let impl_bar = || empty_range(4, 0);
28575    let fn_new = || empty_range(5, 4);
28576
28577    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
28578        cx.update_editor(|e, window, cx| {
28579            e.scroll(
28580                gpui::Point {
28581                    x: 0.,
28582                    y: scroll_offset,
28583                },
28584                None,
28585                window,
28586                cx,
28587            );
28588        });
28589        cx.simulate_click(
28590            gpui::Point {
28591                x: px(0.),
28592                y: click_offset as f32 * line_height,
28593            },
28594            Modifiers::none(),
28595        );
28596        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
28597    };
28598
28599    assert_eq!(
28600        scroll_and_click(
28601            4.5, // impl Bar is halfway off the screen
28602            0.0  // click top of screen
28603        ),
28604        // scrolled to impl Bar
28605        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28606    );
28607
28608    assert_eq!(
28609        scroll_and_click(
28610            4.5,  // impl Bar is halfway off the screen
28611            0.25  // click middle of impl Bar
28612        ),
28613        // scrolled to impl Bar
28614        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28615    );
28616
28617    assert_eq!(
28618        scroll_and_click(
28619            4.5, // impl Bar is halfway off the screen
28620            1.5  // click below impl Bar (e.g. fn new())
28621        ),
28622        // scrolled to fn new() - this is below the impl Bar header which has persisted
28623        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28624    );
28625
28626    assert_eq!(
28627        scroll_and_click(
28628            5.5,  // fn new is halfway underneath impl Bar
28629            0.75  // click on the overlap of impl Bar and fn new()
28630        ),
28631        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28632    );
28633
28634    assert_eq!(
28635        scroll_and_click(
28636            5.5,  // fn new is halfway underneath impl Bar
28637            1.25  // click on the visible part of fn new()
28638        ),
28639        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28640    );
28641
28642    assert_eq!(
28643        scroll_and_click(
28644            1.5, // fn foo is halfway off the screen
28645            0.0  // click top of screen
28646        ),
28647        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28648    );
28649
28650    assert_eq!(
28651        scroll_and_click(
28652            1.5,  // fn foo is halfway off the screen
28653            0.75  // click visible part of let abc...
28654        )
28655        .0,
28656        // no change in scroll
28657        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28658        (gpui::Point { x: 0., y: 1.5 })
28659    );
28660}
28661
28662#[gpui::test]
28663async fn test_next_prev_reference(cx: &mut TestAppContext) {
28664    const CYCLE_POSITIONS: &[&'static str] = &[
28665        indoc! {"
28666            fn foo() {
28667                let ˇabc = 123;
28668                let x = abc + 1;
28669                let y = abc + 2;
28670                let z = abc + 2;
28671            }
28672        "},
28673        indoc! {"
28674            fn foo() {
28675                let abc = 123;
28676                let x = ˇabc + 1;
28677                let y = abc + 2;
28678                let z = abc + 2;
28679            }
28680        "},
28681        indoc! {"
28682            fn foo() {
28683                let abc = 123;
28684                let x = abc + 1;
28685                let y = ˇabc + 2;
28686                let z = abc + 2;
28687            }
28688        "},
28689        indoc! {"
28690            fn foo() {
28691                let abc = 123;
28692                let x = abc + 1;
28693                let y = abc + 2;
28694                let z = ˇabc + 2;
28695            }
28696        "},
28697    ];
28698
28699    init_test(cx, |_| {});
28700
28701    let mut cx = EditorLspTestContext::new_rust(
28702        lsp::ServerCapabilities {
28703            references_provider: Some(lsp::OneOf::Left(true)),
28704            ..Default::default()
28705        },
28706        cx,
28707    )
28708    .await;
28709
28710    // importantly, the cursor is in the middle
28711    cx.set_state(indoc! {"
28712        fn foo() {
28713            let aˇbc = 123;
28714            let x = abc + 1;
28715            let y = abc + 2;
28716            let z = abc + 2;
28717        }
28718    "});
28719
28720    let reference_ranges = [
28721        lsp::Position::new(1, 8),
28722        lsp::Position::new(2, 12),
28723        lsp::Position::new(3, 12),
28724        lsp::Position::new(4, 12),
28725    ]
28726    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28727
28728    cx.lsp
28729        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28730            Ok(Some(
28731                reference_ranges
28732                    .map(|range| lsp::Location {
28733                        uri: params.text_document_position.text_document.uri.clone(),
28734                        range,
28735                    })
28736                    .to_vec(),
28737            ))
28738        });
28739
28740    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28741        cx.update_editor(|editor, window, cx| {
28742            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28743        })
28744        .unwrap()
28745        .await
28746        .unwrap()
28747    };
28748
28749    _move(Direction::Next, 1, &mut cx).await;
28750    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28751
28752    _move(Direction::Next, 1, &mut cx).await;
28753    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28754
28755    _move(Direction::Next, 1, &mut cx).await;
28756    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28757
28758    // loops back to the start
28759    _move(Direction::Next, 1, &mut cx).await;
28760    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28761
28762    // loops back to the end
28763    _move(Direction::Prev, 1, &mut cx).await;
28764    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28765
28766    _move(Direction::Prev, 1, &mut cx).await;
28767    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28768
28769    _move(Direction::Prev, 1, &mut cx).await;
28770    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28771
28772    _move(Direction::Prev, 1, &mut cx).await;
28773    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28774
28775    _move(Direction::Next, 3, &mut cx).await;
28776    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28777
28778    _move(Direction::Prev, 2, &mut cx).await;
28779    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28780}
28781
28782#[gpui::test]
28783async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
28784    init_test(cx, |_| {});
28785
28786    let (editor, cx) = cx.add_window_view(|window, cx| {
28787        let multi_buffer = MultiBuffer::build_multi(
28788            [
28789                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28790                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28791            ],
28792            cx,
28793        );
28794        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28795    });
28796
28797    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28798    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28799
28800    cx.assert_excerpts_with_selections(indoc! {"
28801        [EXCERPT]
28802        ˇ1
28803        2
28804        3
28805        [EXCERPT]
28806        1
28807        2
28808        3
28809        "});
28810
28811    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28812    cx.update_editor(|editor, window, cx| {
28813        editor.change_selections(None.into(), window, cx, |s| {
28814            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28815        });
28816    });
28817    cx.assert_excerpts_with_selections(indoc! {"
28818        [EXCERPT]
28819        1
2882028821        3
28822        [EXCERPT]
28823        1
28824        2
28825        3
28826        "});
28827
28828    cx.update_editor(|editor, window, cx| {
28829        editor
28830            .select_all_matches(&SelectAllMatches, window, cx)
28831            .unwrap();
28832    });
28833    cx.assert_excerpts_with_selections(indoc! {"
28834        [EXCERPT]
28835        1
2883628837        3
28838        [EXCERPT]
28839        1
2884028841        3
28842        "});
28843
28844    cx.update_editor(|editor, window, cx| {
28845        editor.handle_input("X", window, cx);
28846    });
28847    cx.assert_excerpts_with_selections(indoc! {"
28848        [EXCERPT]
28849        1
2885028851        3
28852        [EXCERPT]
28853        1
2885428855        3
28856        "});
28857
28858    // Scenario 2: Select "2", then fold second buffer before insertion
28859    cx.update_multibuffer(|mb, cx| {
28860        for buffer_id in buffer_ids.iter() {
28861            let buffer = mb.buffer(*buffer_id).unwrap();
28862            buffer.update(cx, |buffer, cx| {
28863                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28864            });
28865        }
28866    });
28867
28868    // Select "2" and select all matches
28869    cx.update_editor(|editor, window, cx| {
28870        editor.change_selections(None.into(), window, cx, |s| {
28871            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28872        });
28873        editor
28874            .select_all_matches(&SelectAllMatches, window, cx)
28875            .unwrap();
28876    });
28877
28878    // Fold second buffer - should remove selections from folded buffer
28879    cx.update_editor(|editor, _, cx| {
28880        editor.fold_buffer(buffer_ids[1], cx);
28881    });
28882    cx.assert_excerpts_with_selections(indoc! {"
28883        [EXCERPT]
28884        1
2888528886        3
28887        [EXCERPT]
28888        [FOLDED]
28889        "});
28890
28891    // Insert text - should only affect first buffer
28892    cx.update_editor(|editor, window, cx| {
28893        editor.handle_input("Y", window, cx);
28894    });
28895    cx.update_editor(|editor, _, cx| {
28896        editor.unfold_buffer(buffer_ids[1], cx);
28897    });
28898    cx.assert_excerpts_with_selections(indoc! {"
28899        [EXCERPT]
28900        1
2890128902        3
28903        [EXCERPT]
28904        1
28905        2
28906        3
28907        "});
28908
28909    // Scenario 3: Select "2", then fold first buffer before insertion
28910    cx.update_multibuffer(|mb, cx| {
28911        for buffer_id in buffer_ids.iter() {
28912            let buffer = mb.buffer(*buffer_id).unwrap();
28913            buffer.update(cx, |buffer, cx| {
28914                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28915            });
28916        }
28917    });
28918
28919    // Select "2" and select all matches
28920    cx.update_editor(|editor, window, cx| {
28921        editor.change_selections(None.into(), window, cx, |s| {
28922            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28923        });
28924        editor
28925            .select_all_matches(&SelectAllMatches, window, cx)
28926            .unwrap();
28927    });
28928
28929    // Fold first buffer - should remove selections from folded buffer
28930    cx.update_editor(|editor, _, cx| {
28931        editor.fold_buffer(buffer_ids[0], cx);
28932    });
28933    cx.assert_excerpts_with_selections(indoc! {"
28934        [EXCERPT]
28935        [FOLDED]
28936        [EXCERPT]
28937        1
2893828939        3
28940        "});
28941
28942    // Insert text - should only affect second buffer
28943    cx.update_editor(|editor, window, cx| {
28944        editor.handle_input("Z", window, cx);
28945    });
28946    cx.update_editor(|editor, _, cx| {
28947        editor.unfold_buffer(buffer_ids[0], cx);
28948    });
28949    cx.assert_excerpts_with_selections(indoc! {"
28950        [EXCERPT]
28951        1
28952        2
28953        3
28954        [EXCERPT]
28955        1
2895628957        3
28958        "});
28959
28960    // Test correct folded header is selected upon fold
28961    cx.update_editor(|editor, _, cx| {
28962        editor.fold_buffer(buffer_ids[0], cx);
28963        editor.fold_buffer(buffer_ids[1], cx);
28964    });
28965    cx.assert_excerpts_with_selections(indoc! {"
28966        [EXCERPT]
28967        [FOLDED]
28968        [EXCERPT]
28969        ˇ[FOLDED]
28970        "});
28971
28972    // Test selection inside folded buffer unfolds it on type
28973    cx.update_editor(|editor, window, cx| {
28974        editor.handle_input("W", window, cx);
28975    });
28976    cx.update_editor(|editor, _, cx| {
28977        editor.unfold_buffer(buffer_ids[0], cx);
28978    });
28979    cx.assert_excerpts_with_selections(indoc! {"
28980        [EXCERPT]
28981        1
28982        2
28983        3
28984        [EXCERPT]
28985        Wˇ1
28986        Z
28987        3
28988        "});
28989}
28990
28991#[gpui::test]
28992async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
28993    init_test(cx, |_| {});
28994    let mut leader_cx = EditorTestContext::new(cx).await;
28995
28996    let diff_base = indoc!(
28997        r#"
28998        one
28999        two
29000        three
29001        four
29002        five
29003        six
29004        "#
29005    );
29006
29007    let initial_state = indoc!(
29008        r#"
29009        ˇone
29010        two
29011        THREE
29012        four
29013        five
29014        six
29015        "#
29016    );
29017
29018    leader_cx.set_state(initial_state);
29019
29020    leader_cx.set_head_text(&diff_base);
29021    leader_cx.run_until_parked();
29022
29023    let follower = leader_cx.update_multibuffer(|leader, cx| {
29024        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29025        leader.set_all_diff_hunks_expanded(cx);
29026        leader.get_or_create_follower(cx)
29027    });
29028    follower.update(cx, |follower, cx| {
29029        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29030        follower.set_all_diff_hunks_expanded(cx);
29031    });
29032
29033    let follower_editor =
29034        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
29035    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
29036
29037    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
29038    cx.run_until_parked();
29039
29040    leader_cx.assert_editor_state(initial_state);
29041    follower_cx.assert_editor_state(indoc! {
29042        r#"
29043        ˇone
29044        two
29045        three
29046        four
29047        five
29048        six
29049        "#
29050    });
29051
29052    follower_cx.editor(|editor, _window, cx| {
29053        assert!(editor.read_only(cx));
29054    });
29055
29056    leader_cx.update_editor(|editor, _window, cx| {
29057        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
29058    });
29059    cx.run_until_parked();
29060
29061    leader_cx.assert_editor_state(indoc! {
29062        r#"
29063        ˇone
29064        two
29065        THREE
29066        four
29067        FIVE
29068        six
29069        "#
29070    });
29071
29072    follower_cx.assert_editor_state(indoc! {
29073        r#"
29074        ˇone
29075        two
29076        three
29077        four
29078        five
29079        six
29080        "#
29081    });
29082
29083    leader_cx.update_editor(|editor, _window, cx| {
29084        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
29085    });
29086    cx.run_until_parked();
29087
29088    leader_cx.assert_editor_state(indoc! {
29089        r#"
29090        ˇone
29091        two
29092        THREE
29093        four
29094        FIVE
29095        six
29096        SEVEN"#
29097    });
29098
29099    follower_cx.assert_editor_state(indoc! {
29100        r#"
29101        ˇone
29102        two
29103        three
29104        four
29105        five
29106        six
29107        "#
29108    });
29109
29110    leader_cx.update_editor(|editor, window, cx| {
29111        editor.move_down(&MoveDown, window, cx);
29112        editor.refresh_selected_text_highlights(true, window, cx);
29113    });
29114    leader_cx.run_until_parked();
29115}
29116
29117#[gpui::test]
29118async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
29119    init_test(cx, |_| {});
29120    let base_text = "base\n";
29121    let buffer_text = "buffer\n";
29122
29123    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
29124    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
29125
29126    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
29127    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
29128    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
29129    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
29130
29131    let leader = cx.new(|cx| {
29132        let mut leader = MultiBuffer::new(Capability::ReadWrite);
29133        leader.set_all_diff_hunks_expanded(cx);
29134        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
29135        leader
29136    });
29137    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
29138    follower.update(cx, |follower, _| {
29139        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
29140    });
29141
29142    leader.update(cx, |leader, cx| {
29143        leader.insert_excerpts_after(
29144            ExcerptId::min(),
29145            extra_buffer_2.clone(),
29146            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29147            cx,
29148        );
29149        leader.add_diff(extra_diff_2.clone(), cx);
29150
29151        leader.insert_excerpts_after(
29152            ExcerptId::min(),
29153            extra_buffer_1.clone(),
29154            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29155            cx,
29156        );
29157        leader.add_diff(extra_diff_1.clone(), cx);
29158
29159        leader.insert_excerpts_after(
29160            ExcerptId::min(),
29161            buffer1.clone(),
29162            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29163            cx,
29164        );
29165        leader.add_diff(diff1.clone(), cx);
29166    });
29167
29168    cx.run_until_parked();
29169    let mut cx = cx.add_empty_window();
29170
29171    let leader_editor = cx
29172        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
29173    let follower_editor = cx.new_window_entity(|window, cx| {
29174        Editor::for_multibuffer(follower.clone(), None, window, cx)
29175    });
29176
29177    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
29178    leader_cx.assert_editor_state(indoc! {"
29179       ˇbuffer
29180
29181       dummy text 1
29182
29183       dummy text 2
29184    "});
29185    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
29186    follower_cx.assert_editor_state(indoc! {"
29187        ˇbase
29188
29189
29190    "});
29191}
29192
29193#[gpui::test]
29194async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29195    init_test(cx, |_| {});
29196
29197    let (editor, cx) = cx.add_window_view(|window, cx| {
29198        let multi_buffer = MultiBuffer::build_multi(
29199            [
29200                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29201                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29202            ],
29203            cx,
29204        );
29205        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29206    });
29207
29208    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29209
29210    cx.assert_excerpts_with_selections(indoc! {"
29211        [EXCERPT]
29212        ˇ1
29213        2
29214        3
29215        [EXCERPT]
29216        1
29217        2
29218        3
29219        4
29220        5
29221        6
29222        7
29223        8
29224        9
29225        "});
29226
29227    cx.update_editor(|editor, window, cx| {
29228        editor.change_selections(None.into(), window, cx, |s| {
29229            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29230        });
29231    });
29232
29233    cx.assert_excerpts_with_selections(indoc! {"
29234        [EXCERPT]
29235        1
29236        2
29237        3
29238        [EXCERPT]
29239        1
29240        2
29241        3
29242        4
29243        5
29244        6
29245        ˇ7
29246        8
29247        9
29248        "});
29249
29250    cx.update_editor(|editor, _window, cx| {
29251        editor.set_vertical_scroll_margin(0, cx);
29252    });
29253
29254    cx.update_editor(|editor, window, cx| {
29255        assert_eq!(editor.vertical_scroll_margin(), 0);
29256        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29257        assert_eq!(
29258            editor.snapshot(window, cx).scroll_position(),
29259            gpui::Point::new(0., 12.0)
29260        );
29261    });
29262
29263    cx.update_editor(|editor, _window, cx| {
29264        editor.set_vertical_scroll_margin(3, cx);
29265    });
29266
29267    cx.update_editor(|editor, window, cx| {
29268        assert_eq!(editor.vertical_scroll_margin(), 3);
29269        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29270        assert_eq!(
29271            editor.snapshot(window, cx).scroll_position(),
29272            gpui::Point::new(0., 9.0)
29273        );
29274    });
29275}
29276
29277#[gpui::test]
29278async fn test_find_references_single_case(cx: &mut TestAppContext) {
29279    init_test(cx, |_| {});
29280    let mut cx = EditorLspTestContext::new_rust(
29281        lsp::ServerCapabilities {
29282            references_provider: Some(lsp::OneOf::Left(true)),
29283            ..lsp::ServerCapabilities::default()
29284        },
29285        cx,
29286    )
29287    .await;
29288
29289    let before = indoc!(
29290        r#"
29291        fn main() {
29292            let aˇbc = 123;
29293            let xyz = abc;
29294        }
29295        "#
29296    );
29297    let after = indoc!(
29298        r#"
29299        fn main() {
29300            let abc = 123;
29301            let xyz = ˇabc;
29302        }
29303        "#
29304    );
29305
29306    cx.lsp
29307        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29308            Ok(Some(vec![
29309                lsp::Location {
29310                    uri: params.text_document_position.text_document.uri.clone(),
29311                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29312                },
29313                lsp::Location {
29314                    uri: params.text_document_position.text_document.uri,
29315                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29316                },
29317            ]))
29318        });
29319
29320    cx.set_state(before);
29321
29322    let action = FindAllReferences {
29323        always_open_multibuffer: false,
29324    };
29325
29326    let navigated = cx
29327        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29328        .expect("should have spawned a task")
29329        .await
29330        .unwrap();
29331
29332    assert_eq!(navigated, Navigated::No);
29333
29334    cx.run_until_parked();
29335
29336    cx.assert_editor_state(after);
29337}