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        assert_eq!(
 7754            display_ranges(editor, cx),
 7755            vec![
 7756                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7757                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7758            ]
 7759        );
 7760    });
 7761
 7762    _ = editor.update(cx, |editor, window, cx| {
 7763        editor.select_line(&SelectLine, window, cx);
 7764        assert_eq!(
 7765            display_ranges(editor, cx),
 7766            vec![
 7767                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7768                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7769            ]
 7770        );
 7771    });
 7772
 7773    _ = editor.update(cx, |editor, window, cx| {
 7774        editor.select_line(&SelectLine, window, cx);
 7775        assert_eq!(
 7776            display_ranges(editor, cx),
 7777            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7778        );
 7779    });
 7780}
 7781
 7782#[gpui::test]
 7783async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7784    init_test(cx, |_| {});
 7785    let mut cx = EditorTestContext::new(cx).await;
 7786
 7787    #[track_caller]
 7788    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7789        cx.set_state(initial_state);
 7790        cx.update_editor(|e, window, cx| {
 7791            e.split_selection_into_lines(&Default::default(), window, cx)
 7792        });
 7793        cx.assert_editor_state(expected_state);
 7794    }
 7795
 7796    // Selection starts and ends at the middle of lines, left-to-right
 7797    test(
 7798        &mut cx,
 7799        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7800        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7801    );
 7802    // Same thing, right-to-left
 7803    test(
 7804        &mut cx,
 7805        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7806        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7807    );
 7808
 7809    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7810    test(
 7811        &mut cx,
 7812        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7813        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7814    );
 7815    // Same thing, right-to-left
 7816    test(
 7817        &mut cx,
 7818        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7819        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7820    );
 7821
 7822    // Whole buffer, left-to-right, last line ends with newline
 7823    test(
 7824        &mut cx,
 7825        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7826        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7827    );
 7828    // Same thing, right-to-left
 7829    test(
 7830        &mut cx,
 7831        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7832        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7833    );
 7834
 7835    // Starts at the end of a line, ends at the start of another
 7836    test(
 7837        &mut cx,
 7838        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7839        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7840    );
 7841}
 7842
 7843#[gpui::test]
 7844async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7845    init_test(cx, |_| {});
 7846
 7847    let editor = cx.add_window(|window, cx| {
 7848        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7849        build_editor(buffer, window, cx)
 7850    });
 7851
 7852    // setup
 7853    _ = editor.update(cx, |editor, window, cx| {
 7854        editor.fold_creases(
 7855            vec![
 7856                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7857                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7858                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7859            ],
 7860            true,
 7861            window,
 7862            cx,
 7863        );
 7864        assert_eq!(
 7865            editor.display_text(cx),
 7866            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7867        );
 7868    });
 7869
 7870    _ = editor.update(cx, |editor, window, cx| {
 7871        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7872            s.select_display_ranges([
 7873                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7874                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7875                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7876                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7877            ])
 7878        });
 7879        editor.split_selection_into_lines(&Default::default(), window, cx);
 7880        assert_eq!(
 7881            editor.display_text(cx),
 7882            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7883        );
 7884    });
 7885    EditorTestContext::for_editor(editor, cx)
 7886        .await
 7887        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7888
 7889    _ = editor.update(cx, |editor, window, cx| {
 7890        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7891            s.select_display_ranges([
 7892                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7893            ])
 7894        });
 7895        editor.split_selection_into_lines(&Default::default(), window, cx);
 7896        assert_eq!(
 7897            editor.display_text(cx),
 7898            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7899        );
 7900        assert_eq!(
 7901            display_ranges(editor, cx),
 7902            [
 7903                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7904                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7905                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7906                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7907                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7908                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7909                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7910            ]
 7911        );
 7912    });
 7913    EditorTestContext::for_editor(editor, cx)
 7914        .await
 7915        .assert_editor_state(
 7916            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7917        );
 7918}
 7919
 7920#[gpui::test]
 7921async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7922    init_test(cx, |_| {});
 7923
 7924    let mut cx = EditorTestContext::new(cx).await;
 7925
 7926    cx.set_state(indoc!(
 7927        r#"abc
 7928           defˇghi
 7929
 7930           jk
 7931           nlmo
 7932           "#
 7933    ));
 7934
 7935    cx.update_editor(|editor, window, cx| {
 7936        editor.add_selection_above(&Default::default(), window, cx);
 7937    });
 7938
 7939    cx.assert_editor_state(indoc!(
 7940        r#"abcˇ
 7941           defˇghi
 7942
 7943           jk
 7944           nlmo
 7945           "#
 7946    ));
 7947
 7948    cx.update_editor(|editor, window, cx| {
 7949        editor.add_selection_above(&Default::default(), window, cx);
 7950    });
 7951
 7952    cx.assert_editor_state(indoc!(
 7953        r#"abcˇ
 7954            defˇghi
 7955
 7956            jk
 7957            nlmo
 7958            "#
 7959    ));
 7960
 7961    cx.update_editor(|editor, window, cx| {
 7962        editor.add_selection_below(&Default::default(), window, cx);
 7963    });
 7964
 7965    cx.assert_editor_state(indoc!(
 7966        r#"abc
 7967           defˇghi
 7968
 7969           jk
 7970           nlmo
 7971           "#
 7972    ));
 7973
 7974    cx.update_editor(|editor, window, cx| {
 7975        editor.undo_selection(&Default::default(), window, cx);
 7976    });
 7977
 7978    cx.assert_editor_state(indoc!(
 7979        r#"abcˇ
 7980           defˇghi
 7981
 7982           jk
 7983           nlmo
 7984           "#
 7985    ));
 7986
 7987    cx.update_editor(|editor, window, cx| {
 7988        editor.redo_selection(&Default::default(), window, cx);
 7989    });
 7990
 7991    cx.assert_editor_state(indoc!(
 7992        r#"abc
 7993           defˇghi
 7994
 7995           jk
 7996           nlmo
 7997           "#
 7998    ));
 7999
 8000    cx.update_editor(|editor, window, cx| {
 8001        editor.add_selection_below(&Default::default(), window, cx);
 8002    });
 8003
 8004    cx.assert_editor_state(indoc!(
 8005        r#"abc
 8006           defˇghi
 8007           ˇ
 8008           jk
 8009           nlmo
 8010           "#
 8011    ));
 8012
 8013    cx.update_editor(|editor, window, cx| {
 8014        editor.add_selection_below(&Default::default(), window, cx);
 8015    });
 8016
 8017    cx.assert_editor_state(indoc!(
 8018        r#"abc
 8019           defˇghi
 8020           ˇ
 8021           jkˇ
 8022           nlmo
 8023           "#
 8024    ));
 8025
 8026    cx.update_editor(|editor, window, cx| {
 8027        editor.add_selection_below(&Default::default(), window, cx);
 8028    });
 8029
 8030    cx.assert_editor_state(indoc!(
 8031        r#"abc
 8032           defˇghi
 8033           ˇ
 8034           jkˇ
 8035           nlmˇo
 8036           "#
 8037    ));
 8038
 8039    cx.update_editor(|editor, window, cx| {
 8040        editor.add_selection_below(&Default::default(), window, cx);
 8041    });
 8042
 8043    cx.assert_editor_state(indoc!(
 8044        r#"abc
 8045           defˇghi
 8046           ˇ
 8047           jkˇ
 8048           nlmˇo
 8049           ˇ"#
 8050    ));
 8051
 8052    // change selections
 8053    cx.set_state(indoc!(
 8054        r#"abc
 8055           def«ˇg»hi
 8056
 8057           jk
 8058           nlmo
 8059           "#
 8060    ));
 8061
 8062    cx.update_editor(|editor, window, cx| {
 8063        editor.add_selection_below(&Default::default(), window, cx);
 8064    });
 8065
 8066    cx.assert_editor_state(indoc!(
 8067        r#"abc
 8068           def«ˇg»hi
 8069
 8070           jk
 8071           nlm«ˇo»
 8072           "#
 8073    ));
 8074
 8075    cx.update_editor(|editor, window, cx| {
 8076        editor.add_selection_below(&Default::default(), window, cx);
 8077    });
 8078
 8079    cx.assert_editor_state(indoc!(
 8080        r#"abc
 8081           def«ˇg»hi
 8082
 8083           jk
 8084           nlm«ˇo»
 8085           "#
 8086    ));
 8087
 8088    cx.update_editor(|editor, window, cx| {
 8089        editor.add_selection_above(&Default::default(), window, cx);
 8090    });
 8091
 8092    cx.assert_editor_state(indoc!(
 8093        r#"abc
 8094           def«ˇg»hi
 8095
 8096           jk
 8097           nlmo
 8098           "#
 8099    ));
 8100
 8101    cx.update_editor(|editor, window, cx| {
 8102        editor.add_selection_above(&Default::default(), window, cx);
 8103    });
 8104
 8105    cx.assert_editor_state(indoc!(
 8106        r#"abc
 8107           def«ˇg»hi
 8108
 8109           jk
 8110           nlmo
 8111           "#
 8112    ));
 8113
 8114    // Change selections again
 8115    cx.set_state(indoc!(
 8116        r#"a«bc
 8117           defgˇ»hi
 8118
 8119           jk
 8120           nlmo
 8121           "#
 8122    ));
 8123
 8124    cx.update_editor(|editor, window, cx| {
 8125        editor.add_selection_below(&Default::default(), window, cx);
 8126    });
 8127
 8128    cx.assert_editor_state(indoc!(
 8129        r#"a«bcˇ»
 8130           d«efgˇ»hi
 8131
 8132           j«kˇ»
 8133           nlmo
 8134           "#
 8135    ));
 8136
 8137    cx.update_editor(|editor, window, cx| {
 8138        editor.add_selection_below(&Default::default(), window, cx);
 8139    });
 8140    cx.assert_editor_state(indoc!(
 8141        r#"a«bcˇ»
 8142           d«efgˇ»hi
 8143
 8144           j«kˇ»
 8145           n«lmoˇ»
 8146           "#
 8147    ));
 8148    cx.update_editor(|editor, window, cx| {
 8149        editor.add_selection_above(&Default::default(), window, cx);
 8150    });
 8151
 8152    cx.assert_editor_state(indoc!(
 8153        r#"a«bcˇ»
 8154           d«efgˇ»hi
 8155
 8156           j«kˇ»
 8157           nlmo
 8158           "#
 8159    ));
 8160
 8161    // Change selections again
 8162    cx.set_state(indoc!(
 8163        r#"abc
 8164           d«ˇefghi
 8165
 8166           jk
 8167           nlm»o
 8168           "#
 8169    ));
 8170
 8171    cx.update_editor(|editor, window, cx| {
 8172        editor.add_selection_above(&Default::default(), window, cx);
 8173    });
 8174
 8175    cx.assert_editor_state(indoc!(
 8176        r#"a«ˇbc»
 8177           d«ˇef»ghi
 8178
 8179           j«ˇk»
 8180           n«ˇlm»o
 8181           "#
 8182    ));
 8183
 8184    cx.update_editor(|editor, window, cx| {
 8185        editor.add_selection_below(&Default::default(), window, cx);
 8186    });
 8187
 8188    cx.assert_editor_state(indoc!(
 8189        r#"abc
 8190           d«ˇef»ghi
 8191
 8192           j«ˇk»
 8193           n«ˇlm»o
 8194           "#
 8195    ));
 8196}
 8197
 8198#[gpui::test]
 8199async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8200    init_test(cx, |_| {});
 8201    let mut cx = EditorTestContext::new(cx).await;
 8202
 8203    cx.set_state(indoc!(
 8204        r#"line onˇe
 8205           liˇne two
 8206           line three
 8207           line four"#
 8208    ));
 8209
 8210    cx.update_editor(|editor, window, cx| {
 8211        editor.add_selection_below(&Default::default(), window, cx);
 8212    });
 8213
 8214    // test multiple cursors expand in the same direction
 8215    cx.assert_editor_state(indoc!(
 8216        r#"line onˇe
 8217           liˇne twˇo
 8218           liˇne three
 8219           line four"#
 8220    ));
 8221
 8222    cx.update_editor(|editor, window, cx| {
 8223        editor.add_selection_below(&Default::default(), window, cx);
 8224    });
 8225
 8226    cx.update_editor(|editor, window, cx| {
 8227        editor.add_selection_below(&Default::default(), window, cx);
 8228    });
 8229
 8230    // test multiple cursors expand below overflow
 8231    cx.assert_editor_state(indoc!(
 8232        r#"line onˇe
 8233           liˇne twˇo
 8234           liˇne thˇree
 8235           liˇne foˇur"#
 8236    ));
 8237
 8238    cx.update_editor(|editor, window, cx| {
 8239        editor.add_selection_above(&Default::default(), window, cx);
 8240    });
 8241
 8242    // test multiple cursors retrieves back correctly
 8243    cx.assert_editor_state(indoc!(
 8244        r#"line onˇe
 8245           liˇne twˇo
 8246           liˇne thˇree
 8247           line four"#
 8248    ));
 8249
 8250    cx.update_editor(|editor, window, cx| {
 8251        editor.add_selection_above(&Default::default(), window, cx);
 8252    });
 8253
 8254    cx.update_editor(|editor, window, cx| {
 8255        editor.add_selection_above(&Default::default(), window, cx);
 8256    });
 8257
 8258    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8259    cx.assert_editor_state(indoc!(
 8260        r#"liˇne onˇe
 8261           liˇne two
 8262           line three
 8263           line four"#
 8264    ));
 8265
 8266    cx.update_editor(|editor, window, cx| {
 8267        editor.undo_selection(&Default::default(), window, cx);
 8268    });
 8269
 8270    // test undo
 8271    cx.assert_editor_state(indoc!(
 8272        r#"line onˇe
 8273           liˇne twˇo
 8274           line three
 8275           line four"#
 8276    ));
 8277
 8278    cx.update_editor(|editor, window, cx| {
 8279        editor.redo_selection(&Default::default(), window, cx);
 8280    });
 8281
 8282    // test redo
 8283    cx.assert_editor_state(indoc!(
 8284        r#"liˇne onˇe
 8285           liˇne two
 8286           line three
 8287           line four"#
 8288    ));
 8289
 8290    cx.set_state(indoc!(
 8291        r#"abcd
 8292           ef«ghˇ»
 8293           ijkl
 8294           «mˇ»nop"#
 8295    ));
 8296
 8297    cx.update_editor(|editor, window, cx| {
 8298        editor.add_selection_above(&Default::default(), window, cx);
 8299    });
 8300
 8301    // test multiple selections expand in the same direction
 8302    cx.assert_editor_state(indoc!(
 8303        r#"ab«cdˇ»
 8304           ef«ghˇ»
 8305           «iˇ»jkl
 8306           «mˇ»nop"#
 8307    ));
 8308
 8309    cx.update_editor(|editor, window, cx| {
 8310        editor.add_selection_above(&Default::default(), window, cx);
 8311    });
 8312
 8313    // test multiple selection upward overflow
 8314    cx.assert_editor_state(indoc!(
 8315        r#"ab«cdˇ»
 8316           «eˇ»f«ghˇ»
 8317           «iˇ»jkl
 8318           «mˇ»nop"#
 8319    ));
 8320
 8321    cx.update_editor(|editor, window, cx| {
 8322        editor.add_selection_below(&Default::default(), window, cx);
 8323    });
 8324
 8325    // test multiple selection retrieves back correctly
 8326    cx.assert_editor_state(indoc!(
 8327        r#"abcd
 8328           ef«ghˇ»
 8329           «iˇ»jkl
 8330           «mˇ»nop"#
 8331    ));
 8332
 8333    cx.update_editor(|editor, window, cx| {
 8334        editor.add_selection_below(&Default::default(), window, cx);
 8335    });
 8336
 8337    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8338    cx.assert_editor_state(indoc!(
 8339        r#"abcd
 8340           ef«ghˇ»
 8341           ij«klˇ»
 8342           «mˇ»nop"#
 8343    ));
 8344
 8345    cx.update_editor(|editor, window, cx| {
 8346        editor.undo_selection(&Default::default(), window, cx);
 8347    });
 8348
 8349    // test undo
 8350    cx.assert_editor_state(indoc!(
 8351        r#"abcd
 8352           ef«ghˇ»
 8353           «iˇ»jkl
 8354           «mˇ»nop"#
 8355    ));
 8356
 8357    cx.update_editor(|editor, window, cx| {
 8358        editor.redo_selection(&Default::default(), window, cx);
 8359    });
 8360
 8361    // test redo
 8362    cx.assert_editor_state(indoc!(
 8363        r#"abcd
 8364           ef«ghˇ»
 8365           ij«klˇ»
 8366           «mˇ»nop"#
 8367    ));
 8368}
 8369
 8370#[gpui::test]
 8371async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8372    init_test(cx, |_| {});
 8373    let mut cx = EditorTestContext::new(cx).await;
 8374
 8375    cx.set_state(indoc!(
 8376        r#"line onˇe
 8377           liˇne two
 8378           line three
 8379           line four"#
 8380    ));
 8381
 8382    cx.update_editor(|editor, window, cx| {
 8383        editor.add_selection_below(&Default::default(), window, cx);
 8384        editor.add_selection_below(&Default::default(), window, cx);
 8385        editor.add_selection_below(&Default::default(), window, cx);
 8386    });
 8387
 8388    // initial state with two multi cursor groups
 8389    cx.assert_editor_state(indoc!(
 8390        r#"line onˇe
 8391           liˇne twˇo
 8392           liˇne thˇree
 8393           liˇne foˇur"#
 8394    ));
 8395
 8396    // add single cursor in middle - simulate opt click
 8397    cx.update_editor(|editor, window, cx| {
 8398        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8399        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8400        editor.end_selection(window, cx);
 8401    });
 8402
 8403    cx.assert_editor_state(indoc!(
 8404        r#"line onˇe
 8405           liˇne twˇo
 8406           liˇneˇ thˇree
 8407           liˇne foˇur"#
 8408    ));
 8409
 8410    cx.update_editor(|editor, window, cx| {
 8411        editor.add_selection_above(&Default::default(), window, cx);
 8412    });
 8413
 8414    // test new added selection expands above and existing selection shrinks
 8415    cx.assert_editor_state(indoc!(
 8416        r#"line onˇe
 8417           liˇneˇ twˇo
 8418           liˇneˇ thˇree
 8419           line four"#
 8420    ));
 8421
 8422    cx.update_editor(|editor, window, cx| {
 8423        editor.add_selection_above(&Default::default(), window, cx);
 8424    });
 8425
 8426    // test new added selection expands above and existing selection shrinks
 8427    cx.assert_editor_state(indoc!(
 8428        r#"lineˇ onˇe
 8429           liˇneˇ twˇo
 8430           lineˇ three
 8431           line four"#
 8432    ));
 8433
 8434    // intial state with two selection groups
 8435    cx.set_state(indoc!(
 8436        r#"abcd
 8437           ef«ghˇ»
 8438           ijkl
 8439           «mˇ»nop"#
 8440    ));
 8441
 8442    cx.update_editor(|editor, window, cx| {
 8443        editor.add_selection_above(&Default::default(), window, cx);
 8444        editor.add_selection_above(&Default::default(), window, cx);
 8445    });
 8446
 8447    cx.assert_editor_state(indoc!(
 8448        r#"ab«cdˇ»
 8449           «eˇ»f«ghˇ»
 8450           «iˇ»jkl
 8451           «mˇ»nop"#
 8452    ));
 8453
 8454    // add single selection in middle - simulate opt drag
 8455    cx.update_editor(|editor, window, cx| {
 8456        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8457        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8458        editor.update_selection(
 8459            DisplayPoint::new(DisplayRow(2), 4),
 8460            0,
 8461            gpui::Point::<f32>::default(),
 8462            window,
 8463            cx,
 8464        );
 8465        editor.end_selection(window, cx);
 8466    });
 8467
 8468    cx.assert_editor_state(indoc!(
 8469        r#"ab«cdˇ»
 8470           «eˇ»f«ghˇ»
 8471           «iˇ»jk«lˇ»
 8472           «mˇ»nop"#
 8473    ));
 8474
 8475    cx.update_editor(|editor, window, cx| {
 8476        editor.add_selection_below(&Default::default(), window, cx);
 8477    });
 8478
 8479    // test new added selection expands below, others shrinks from above
 8480    cx.assert_editor_state(indoc!(
 8481        r#"abcd
 8482           ef«ghˇ»
 8483           «iˇ»jk«lˇ»
 8484           «mˇ»no«pˇ»"#
 8485    ));
 8486}
 8487
 8488#[gpui::test]
 8489async fn test_select_next(cx: &mut TestAppContext) {
 8490    init_test(cx, |_| {});
 8491    let mut cx = EditorTestContext::new(cx).await;
 8492
 8493    // Enable case sensitive search.
 8494    update_test_editor_settings(&mut cx, |settings| {
 8495        let mut search_settings = SearchSettingsContent::default();
 8496        search_settings.case_sensitive = Some(true);
 8497        settings.search = Some(search_settings);
 8498    });
 8499
 8500    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8501
 8502    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8503        .unwrap();
 8504    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8505
 8506    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8507        .unwrap();
 8508    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8509
 8510    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8511    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8512
 8513    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8514    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8515
 8516    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8517        .unwrap();
 8518    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8519
 8520    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8521        .unwrap();
 8522    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8523
 8524    // Test selection direction should be preserved
 8525    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8526
 8527    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8528        .unwrap();
 8529    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8530
 8531    // Test case sensitivity
 8532    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8533    cx.update_editor(|e, window, cx| {
 8534        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8535    });
 8536    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8537
 8538    // Disable case sensitive search.
 8539    update_test_editor_settings(&mut cx, |settings| {
 8540        let mut search_settings = SearchSettingsContent::default();
 8541        search_settings.case_sensitive = Some(false);
 8542        settings.search = Some(search_settings);
 8543    });
 8544
 8545    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8546    cx.update_editor(|e, window, cx| {
 8547        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8548        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8549    });
 8550    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8551}
 8552
 8553#[gpui::test]
 8554async fn test_select_all_matches(cx: &mut TestAppContext) {
 8555    init_test(cx, |_| {});
 8556    let mut cx = EditorTestContext::new(cx).await;
 8557
 8558    // Enable case sensitive search.
 8559    update_test_editor_settings(&mut cx, |settings| {
 8560        let mut search_settings = SearchSettingsContent::default();
 8561        search_settings.case_sensitive = Some(true);
 8562        settings.search = Some(search_settings);
 8563    });
 8564
 8565    // Test caret-only selections
 8566    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8567    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8568        .unwrap();
 8569    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8570
 8571    // Test left-to-right selections
 8572    cx.set_state("abc\n«abcˇ»\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ˇ»\n«abcˇ»");
 8576
 8577    // Test right-to-left 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 selecting whitespace with caret selection
 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\nabc");
 8588
 8589    // Test selecting whitespace with left-to-right 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 no matches with right-to-left 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 with a single word and clip_at_line_ends=true (#29823)
 8602    cx.set_state("aˇbc");
 8603    cx.update_editor(|e, window, cx| {
 8604        e.set_clip_at_line_ends(true, cx);
 8605        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8606        e.set_clip_at_line_ends(false, cx);
 8607    });
 8608    cx.assert_editor_state("«abcˇ»");
 8609
 8610    // Test case sensitivity
 8611    cx.set_state("fˇoo\nFOO\nFoo");
 8612    cx.update_editor(|e, window, cx| {
 8613        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8614    });
 8615    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8616
 8617    // Disable case sensitive search.
 8618    update_test_editor_settings(&mut cx, |settings| {
 8619        let mut search_settings = SearchSettingsContent::default();
 8620        search_settings.case_sensitive = Some(false);
 8621        settings.search = Some(search_settings);
 8622    });
 8623
 8624    cx.set_state("fˇoo\nFOO\nFoo");
 8625    cx.update_editor(|e, window, cx| {
 8626        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8627    });
 8628    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8629}
 8630
 8631#[gpui::test]
 8632async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8633    init_test(cx, |_| {});
 8634
 8635    let mut cx = EditorTestContext::new(cx).await;
 8636
 8637    let large_body_1 = "\nd".repeat(200);
 8638    let large_body_2 = "\ne".repeat(200);
 8639
 8640    cx.set_state(&format!(
 8641        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8642    ));
 8643    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8644        let scroll_position = editor.scroll_position(cx);
 8645        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8646        scroll_position
 8647    });
 8648
 8649    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8650        .unwrap();
 8651    cx.assert_editor_state(&format!(
 8652        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8653    ));
 8654    let scroll_position_after_selection =
 8655        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8656    assert_eq!(
 8657        initial_scroll_position, scroll_position_after_selection,
 8658        "Scroll position should not change after selecting all matches"
 8659    );
 8660}
 8661
 8662#[gpui::test]
 8663async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8664    init_test(cx, |_| {});
 8665
 8666    let mut cx = EditorLspTestContext::new_rust(
 8667        lsp::ServerCapabilities {
 8668            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8669            ..Default::default()
 8670        },
 8671        cx,
 8672    )
 8673    .await;
 8674
 8675    cx.set_state(indoc! {"
 8676        line 1
 8677        line 2
 8678        linˇe 3
 8679        line 4
 8680        line 5
 8681    "});
 8682
 8683    // Make an edit
 8684    cx.update_editor(|editor, window, cx| {
 8685        editor.handle_input("X", window, cx);
 8686    });
 8687
 8688    // Move cursor to a different position
 8689    cx.update_editor(|editor, window, cx| {
 8690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8691            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8692        });
 8693    });
 8694
 8695    cx.assert_editor_state(indoc! {"
 8696        line 1
 8697        line 2
 8698        linXe 3
 8699        line 4
 8700        liˇne 5
 8701    "});
 8702
 8703    cx.lsp
 8704        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8705            Ok(Some(vec![lsp::TextEdit::new(
 8706                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8707                "PREFIX ".to_string(),
 8708            )]))
 8709        });
 8710
 8711    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8712        .unwrap()
 8713        .await
 8714        .unwrap();
 8715
 8716    cx.assert_editor_state(indoc! {"
 8717        PREFIX line 1
 8718        line 2
 8719        linXe 3
 8720        line 4
 8721        liˇne 5
 8722    "});
 8723
 8724    // Undo formatting
 8725    cx.update_editor(|editor, window, cx| {
 8726        editor.undo(&Default::default(), window, cx);
 8727    });
 8728
 8729    // Verify cursor moved back to position after edit
 8730    cx.assert_editor_state(indoc! {"
 8731        line 1
 8732        line 2
 8733        linXˇe 3
 8734        line 4
 8735        line 5
 8736    "});
 8737}
 8738
 8739#[gpui::test]
 8740async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8741    init_test(cx, |_| {});
 8742
 8743    let mut cx = EditorTestContext::new(cx).await;
 8744
 8745    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8746    cx.update_editor(|editor, window, cx| {
 8747        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8748    });
 8749
 8750    cx.set_state(indoc! {"
 8751        line 1
 8752        line 2
 8753        linˇe 3
 8754        line 4
 8755        line 5
 8756        line 6
 8757        line 7
 8758        line 8
 8759        line 9
 8760        line 10
 8761    "});
 8762
 8763    let snapshot = cx.buffer_snapshot();
 8764    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8765
 8766    cx.update(|_, cx| {
 8767        provider.update(cx, |provider, _| {
 8768            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 8769                id: None,
 8770                edits: vec![(edit_position..edit_position, "X".into())],
 8771                edit_preview: None,
 8772            }))
 8773        })
 8774    });
 8775
 8776    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8777    cx.update_editor(|editor, window, cx| {
 8778        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8779    });
 8780
 8781    cx.assert_editor_state(indoc! {"
 8782        line 1
 8783        line 2
 8784        lineXˇ 3
 8785        line 4
 8786        line 5
 8787        line 6
 8788        line 7
 8789        line 8
 8790        line 9
 8791        line 10
 8792    "});
 8793
 8794    cx.update_editor(|editor, window, cx| {
 8795        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8796            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8797        });
 8798    });
 8799
 8800    cx.assert_editor_state(indoc! {"
 8801        line 1
 8802        line 2
 8803        lineX 3
 8804        line 4
 8805        line 5
 8806        line 6
 8807        line 7
 8808        line 8
 8809        line 9
 8810        liˇne 10
 8811    "});
 8812
 8813    cx.update_editor(|editor, window, cx| {
 8814        editor.undo(&Default::default(), window, cx);
 8815    });
 8816
 8817    cx.assert_editor_state(indoc! {"
 8818        line 1
 8819        line 2
 8820        lineˇ 3
 8821        line 4
 8822        line 5
 8823        line 6
 8824        line 7
 8825        line 8
 8826        line 9
 8827        line 10
 8828    "});
 8829}
 8830
 8831#[gpui::test]
 8832async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8833    init_test(cx, |_| {});
 8834
 8835    let mut cx = EditorTestContext::new(cx).await;
 8836    cx.set_state(
 8837        r#"let foo = 2;
 8838lˇet foo = 2;
 8839let fooˇ = 2;
 8840let foo = 2;
 8841let foo = ˇ2;"#,
 8842    );
 8843
 8844    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8845        .unwrap();
 8846    cx.assert_editor_state(
 8847        r#"let foo = 2;
 8848«letˇ» foo = 2;
 8849let «fooˇ» = 2;
 8850let foo = 2;
 8851let foo = «2ˇ»;"#,
 8852    );
 8853
 8854    // noop for multiple selections with different contents
 8855    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8856        .unwrap();
 8857    cx.assert_editor_state(
 8858        r#"let foo = 2;
 8859«letˇ» foo = 2;
 8860let «fooˇ» = 2;
 8861let foo = 2;
 8862let foo = «2ˇ»;"#,
 8863    );
 8864
 8865    // Test last selection direction should be preserved
 8866    cx.set_state(
 8867        r#"let foo = 2;
 8868let foo = 2;
 8869let «fooˇ» = 2;
 8870let «ˇfoo» = 2;
 8871let foo = 2;"#,
 8872    );
 8873
 8874    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8875        .unwrap();
 8876    cx.assert_editor_state(
 8877        r#"let foo = 2;
 8878let foo = 2;
 8879let «fooˇ» = 2;
 8880let «ˇfoo» = 2;
 8881let «ˇfoo» = 2;"#,
 8882    );
 8883}
 8884
 8885#[gpui::test]
 8886async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8887    init_test(cx, |_| {});
 8888
 8889    let mut cx =
 8890        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8891
 8892    cx.assert_editor_state(indoc! {"
 8893        ˇbbb
 8894        ccc
 8895
 8896        bbb
 8897        ccc
 8898        "});
 8899    cx.dispatch_action(SelectPrevious::default());
 8900    cx.assert_editor_state(indoc! {"
 8901                «bbbˇ»
 8902                ccc
 8903
 8904                bbb
 8905                ccc
 8906                "});
 8907    cx.dispatch_action(SelectPrevious::default());
 8908    cx.assert_editor_state(indoc! {"
 8909                «bbbˇ»
 8910                ccc
 8911
 8912                «bbbˇ»
 8913                ccc
 8914                "});
 8915}
 8916
 8917#[gpui::test]
 8918async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8919    init_test(cx, |_| {});
 8920
 8921    let mut cx = EditorTestContext::new(cx).await;
 8922    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8923
 8924    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8925        .unwrap();
 8926    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8927
 8928    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8929        .unwrap();
 8930    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8931
 8932    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8933    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8934
 8935    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8936    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8937
 8938    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8939        .unwrap();
 8940    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8941
 8942    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8943        .unwrap();
 8944    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8945}
 8946
 8947#[gpui::test]
 8948async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8949    init_test(cx, |_| {});
 8950
 8951    let mut cx = EditorTestContext::new(cx).await;
 8952    cx.set_state("");
 8953
 8954    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8955        .unwrap();
 8956    cx.assert_editor_state("«aˇ»");
 8957    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8958        .unwrap();
 8959    cx.assert_editor_state("«aˇ»");
 8960}
 8961
 8962#[gpui::test]
 8963async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8964    init_test(cx, |_| {});
 8965
 8966    let mut cx = EditorTestContext::new(cx).await;
 8967    cx.set_state(
 8968        r#"let foo = 2;
 8969lˇet foo = 2;
 8970let fooˇ = 2;
 8971let foo = 2;
 8972let foo = ˇ2;"#,
 8973    );
 8974
 8975    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8976        .unwrap();
 8977    cx.assert_editor_state(
 8978        r#"let foo = 2;
 8979«letˇ» foo = 2;
 8980let «fooˇ» = 2;
 8981let foo = 2;
 8982let foo = «2ˇ»;"#,
 8983    );
 8984
 8985    // noop for multiple selections with different contents
 8986    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8987        .unwrap();
 8988    cx.assert_editor_state(
 8989        r#"let foo = 2;
 8990«letˇ» foo = 2;
 8991let «fooˇ» = 2;
 8992let foo = 2;
 8993let foo = «2ˇ»;"#,
 8994    );
 8995}
 8996
 8997#[gpui::test]
 8998async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8999    init_test(cx, |_| {});
 9000    let mut cx = EditorTestContext::new(cx).await;
 9001
 9002    // Enable case sensitive search.
 9003    update_test_editor_settings(&mut cx, |settings| {
 9004        let mut search_settings = SearchSettingsContent::default();
 9005        search_settings.case_sensitive = Some(true);
 9006        settings.search = Some(search_settings);
 9007    });
 9008
 9009    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 9010
 9011    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9012        .unwrap();
 9013    // selection direction is preserved
 9014    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9015
 9016    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9017        .unwrap();
 9018    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9019
 9020    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 9021    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 9022
 9023    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 9024    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 9025
 9026    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9027        .unwrap();
 9028    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 9029
 9030    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 9031        .unwrap();
 9032    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 9033
 9034    // Test case sensitivity
 9035    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 9036    cx.update_editor(|e, window, cx| {
 9037        e.select_previous(&SelectPrevious::default(), window, cx)
 9038            .unwrap();
 9039        e.select_previous(&SelectPrevious::default(), window, cx)
 9040            .unwrap();
 9041    });
 9042    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 9043
 9044    // Disable case sensitive search.
 9045    update_test_editor_settings(&mut cx, |settings| {
 9046        let mut search_settings = SearchSettingsContent::default();
 9047        search_settings.case_sensitive = Some(false);
 9048        settings.search = Some(search_settings);
 9049    });
 9050
 9051    cx.set_state("foo\nFOO\n«ˇFoo»");
 9052    cx.update_editor(|e, window, cx| {
 9053        e.select_previous(&SelectPrevious::default(), window, cx)
 9054            .unwrap();
 9055        e.select_previous(&SelectPrevious::default(), window, cx)
 9056            .unwrap();
 9057    });
 9058    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 9059}
 9060
 9061#[gpui::test]
 9062async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 9063    init_test(cx, |_| {});
 9064
 9065    let language = Arc::new(Language::new(
 9066        LanguageConfig::default(),
 9067        Some(tree_sitter_rust::LANGUAGE.into()),
 9068    ));
 9069
 9070    let text = r#"
 9071        use mod1::mod2::{mod3, mod4};
 9072
 9073        fn fn_1(param1: bool, param2: &str) {
 9074            let var1 = "text";
 9075        }
 9076    "#
 9077    .unindent();
 9078
 9079    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9080    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9081    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9082
 9083    editor
 9084        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9085        .await;
 9086
 9087    editor.update_in(cx, |editor, window, cx| {
 9088        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9089            s.select_display_ranges([
 9090                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 9091                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 9092                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 9093            ]);
 9094        });
 9095        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9096    });
 9097    editor.update(cx, |editor, cx| {
 9098        assert_text_with_selections(
 9099            editor,
 9100            indoc! {r#"
 9101                use mod1::mod2::{mod3, «mod4ˇ»};
 9102
 9103                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9104                    let var1 = "«ˇtext»";
 9105                }
 9106            "#},
 9107            cx,
 9108        );
 9109    });
 9110
 9111    editor.update_in(cx, |editor, window, cx| {
 9112        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9113    });
 9114    editor.update(cx, |editor, cx| {
 9115        assert_text_with_selections(
 9116            editor,
 9117            indoc! {r#"
 9118                use mod1::mod2::«{mod3, mod4}ˇ»;
 9119
 9120                «ˇfn fn_1(param1: bool, param2: &str) {
 9121                    let var1 = "text";
 9122 9123            "#},
 9124            cx,
 9125        );
 9126    });
 9127
 9128    editor.update_in(cx, |editor, window, cx| {
 9129        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9130    });
 9131    assert_eq!(
 9132        editor.update(cx, |editor, cx| editor
 9133            .selections
 9134            .display_ranges(&editor.display_snapshot(cx))),
 9135        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9136    );
 9137
 9138    // Trying to expand the selected syntax node one more time has no effect.
 9139    editor.update_in(cx, |editor, window, cx| {
 9140        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9141    });
 9142    assert_eq!(
 9143        editor.update(cx, |editor, cx| editor
 9144            .selections
 9145            .display_ranges(&editor.display_snapshot(cx))),
 9146        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9147    );
 9148
 9149    editor.update_in(cx, |editor, window, cx| {
 9150        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9151    });
 9152    editor.update(cx, |editor, cx| {
 9153        assert_text_with_selections(
 9154            editor,
 9155            indoc! {r#"
 9156                use mod1::mod2::«{mod3, mod4}ˇ»;
 9157
 9158                «ˇfn fn_1(param1: bool, param2: &str) {
 9159                    let var1 = "text";
 9160 9161            "#},
 9162            cx,
 9163        );
 9164    });
 9165
 9166    editor.update_in(cx, |editor, window, cx| {
 9167        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9168    });
 9169    editor.update(cx, |editor, cx| {
 9170        assert_text_with_selections(
 9171            editor,
 9172            indoc! {r#"
 9173                use mod1::mod2::{mod3, «mod4ˇ»};
 9174
 9175                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9176                    let var1 = "«ˇtext»";
 9177                }
 9178            "#},
 9179            cx,
 9180        );
 9181    });
 9182
 9183    editor.update_in(cx, |editor, window, cx| {
 9184        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9185    });
 9186    editor.update(cx, |editor, cx| {
 9187        assert_text_with_selections(
 9188            editor,
 9189            indoc! {r#"
 9190                use mod1::mod2::{mod3, moˇd4};
 9191
 9192                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9193                    let var1 = "teˇxt";
 9194                }
 9195            "#},
 9196            cx,
 9197        );
 9198    });
 9199
 9200    // Trying to shrink the selected syntax node one more time has no effect.
 9201    editor.update_in(cx, |editor, window, cx| {
 9202        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9203    });
 9204    editor.update_in(cx, |editor, _, cx| {
 9205        assert_text_with_selections(
 9206            editor,
 9207            indoc! {r#"
 9208                use mod1::mod2::{mod3, moˇd4};
 9209
 9210                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9211                    let var1 = "teˇxt";
 9212                }
 9213            "#},
 9214            cx,
 9215        );
 9216    });
 9217
 9218    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9219    // a fold.
 9220    editor.update_in(cx, |editor, window, cx| {
 9221        editor.fold_creases(
 9222            vec![
 9223                Crease::simple(
 9224                    Point::new(0, 21)..Point::new(0, 24),
 9225                    FoldPlaceholder::test(),
 9226                ),
 9227                Crease::simple(
 9228                    Point::new(3, 20)..Point::new(3, 22),
 9229                    FoldPlaceholder::test(),
 9230                ),
 9231            ],
 9232            true,
 9233            window,
 9234            cx,
 9235        );
 9236        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9237    });
 9238    editor.update(cx, |editor, cx| {
 9239        assert_text_with_selections(
 9240            editor,
 9241            indoc! {r#"
 9242                use mod1::mod2::«{mod3, mod4}ˇ»;
 9243
 9244                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9245                    let var1 = "«ˇtext»";
 9246                }
 9247            "#},
 9248            cx,
 9249        );
 9250    });
 9251}
 9252
 9253#[gpui::test]
 9254async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9255    init_test(cx, |_| {});
 9256
 9257    let language = Arc::new(Language::new(
 9258        LanguageConfig::default(),
 9259        Some(tree_sitter_rust::LANGUAGE.into()),
 9260    ));
 9261
 9262    let text = "let a = 2;";
 9263
 9264    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9265    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9266    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9267
 9268    editor
 9269        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9270        .await;
 9271
 9272    // Test case 1: Cursor at end of word
 9273    editor.update_in(cx, |editor, window, cx| {
 9274        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9275            s.select_display_ranges([
 9276                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9277            ]);
 9278        });
 9279    });
 9280    editor.update(cx, |editor, cx| {
 9281        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9282    });
 9283    editor.update_in(cx, |editor, window, cx| {
 9284        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 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
 9296    // Test case 2: Cursor at end of statement
 9297    editor.update_in(cx, |editor, window, cx| {
 9298        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9299            s.select_display_ranges([
 9300                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9301            ]);
 9302        });
 9303    });
 9304    editor.update(cx, |editor, cx| {
 9305        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9306    });
 9307    editor.update_in(cx, |editor, window, cx| {
 9308        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9309    });
 9310    editor.update(cx, |editor, cx| {
 9311        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9312    });
 9313}
 9314
 9315#[gpui::test]
 9316async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9317    init_test(cx, |_| {});
 9318
 9319    let language = Arc::new(Language::new(
 9320        LanguageConfig {
 9321            name: "JavaScript".into(),
 9322            ..Default::default()
 9323        },
 9324        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9325    ));
 9326
 9327    let text = r#"
 9328        let a = {
 9329            key: "value",
 9330        };
 9331    "#
 9332    .unindent();
 9333
 9334    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9335    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9336    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9337
 9338    editor
 9339        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9340        .await;
 9341
 9342    // Test case 1: Cursor after '{'
 9343    editor.update_in(cx, |editor, window, cx| {
 9344        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9345            s.select_display_ranges([
 9346                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9347            ]);
 9348        });
 9349    });
 9350    editor.update(cx, |editor, cx| {
 9351        assert_text_with_selections(
 9352            editor,
 9353            indoc! {r#"
 9354                let a = {ˇ
 9355                    key: "value",
 9356                };
 9357            "#},
 9358            cx,
 9359        );
 9360    });
 9361    editor.update_in(cx, |editor, window, cx| {
 9362        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9363    });
 9364    editor.update(cx, |editor, cx| {
 9365        assert_text_with_selections(
 9366            editor,
 9367            indoc! {r#"
 9368                let a = «ˇ{
 9369                    key: "value",
 9370                }»;
 9371            "#},
 9372            cx,
 9373        );
 9374    });
 9375
 9376    // Test case 2: Cursor after ':'
 9377    editor.update_in(cx, |editor, window, cx| {
 9378        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9379            s.select_display_ranges([
 9380                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9381            ]);
 9382        });
 9383    });
 9384    editor.update(cx, |editor, cx| {
 9385        assert_text_with_selections(
 9386            editor,
 9387            indoc! {r#"
 9388                let a = {
 9389                    key:ˇ "value",
 9390                };
 9391            "#},
 9392            cx,
 9393        );
 9394    });
 9395    editor.update_in(cx, |editor, window, cx| {
 9396        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9397    });
 9398    editor.update(cx, |editor, cx| {
 9399        assert_text_with_selections(
 9400            editor,
 9401            indoc! {r#"
 9402                let a = {
 9403                    «ˇkey: "value"»,
 9404                };
 9405            "#},
 9406            cx,
 9407        );
 9408    });
 9409    editor.update_in(cx, |editor, window, cx| {
 9410        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9411    });
 9412    editor.update(cx, |editor, cx| {
 9413        assert_text_with_selections(
 9414            editor,
 9415            indoc! {r#"
 9416                let a = «ˇ{
 9417                    key: "value",
 9418                }»;
 9419            "#},
 9420            cx,
 9421        );
 9422    });
 9423
 9424    // Test case 3: Cursor after ','
 9425    editor.update_in(cx, |editor, window, cx| {
 9426        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9427            s.select_display_ranges([
 9428                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9429            ]);
 9430        });
 9431    });
 9432    editor.update(cx, |editor, cx| {
 9433        assert_text_with_selections(
 9434            editor,
 9435            indoc! {r#"
 9436                let a = {
 9437                    key: "value",ˇ
 9438                };
 9439            "#},
 9440            cx,
 9441        );
 9442    });
 9443    editor.update_in(cx, |editor, window, cx| {
 9444        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9445    });
 9446    editor.update(cx, |editor, cx| {
 9447        assert_text_with_selections(
 9448            editor,
 9449            indoc! {r#"
 9450                let a = «ˇ{
 9451                    key: "value",
 9452                }»;
 9453            "#},
 9454            cx,
 9455        );
 9456    });
 9457
 9458    // Test case 4: Cursor after ';'
 9459    editor.update_in(cx, |editor, window, cx| {
 9460        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9461            s.select_display_ranges([
 9462                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9463            ]);
 9464        });
 9465    });
 9466    editor.update(cx, |editor, cx| {
 9467        assert_text_with_selections(
 9468            editor,
 9469            indoc! {r#"
 9470                let a = {
 9471                    key: "value",
 9472                };ˇ
 9473            "#},
 9474            cx,
 9475        );
 9476    });
 9477    editor.update_in(cx, |editor, window, cx| {
 9478        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9479    });
 9480    editor.update(cx, |editor, cx| {
 9481        assert_text_with_selections(
 9482            editor,
 9483            indoc! {r#"
 9484                «ˇlet a = {
 9485                    key: "value",
 9486                };
 9487                »"#},
 9488            cx,
 9489        );
 9490    });
 9491}
 9492
 9493#[gpui::test]
 9494async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9495    init_test(cx, |_| {});
 9496
 9497    let language = Arc::new(Language::new(
 9498        LanguageConfig::default(),
 9499        Some(tree_sitter_rust::LANGUAGE.into()),
 9500    ));
 9501
 9502    let text = r#"
 9503        use mod1::mod2::{mod3, mod4};
 9504
 9505        fn fn_1(param1: bool, param2: &str) {
 9506            let var1 = "hello world";
 9507        }
 9508    "#
 9509    .unindent();
 9510
 9511    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9512    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9513    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9514
 9515    editor
 9516        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9517        .await;
 9518
 9519    // Test 1: Cursor on a letter of a string word
 9520    editor.update_in(cx, |editor, window, cx| {
 9521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9522            s.select_display_ranges([
 9523                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9524            ]);
 9525        });
 9526    });
 9527    editor.update_in(cx, |editor, window, cx| {
 9528        assert_text_with_selections(
 9529            editor,
 9530            indoc! {r#"
 9531                use mod1::mod2::{mod3, mod4};
 9532
 9533                fn fn_1(param1: bool, param2: &str) {
 9534                    let var1 = "hˇello world";
 9535                }
 9536            "#},
 9537            cx,
 9538        );
 9539        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9540        assert_text_with_selections(
 9541            editor,
 9542            indoc! {r#"
 9543                use mod1::mod2::{mod3, mod4};
 9544
 9545                fn fn_1(param1: bool, param2: &str) {
 9546                    let var1 = "«ˇhello» world";
 9547                }
 9548            "#},
 9549            cx,
 9550        );
 9551    });
 9552
 9553    // Test 2: Partial selection within a word
 9554    editor.update_in(cx, |editor, window, cx| {
 9555        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9556            s.select_display_ranges([
 9557                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9558            ]);
 9559        });
 9560    });
 9561    editor.update_in(cx, |editor, window, cx| {
 9562        assert_text_with_selections(
 9563            editor,
 9564            indoc! {r#"
 9565                use mod1::mod2::{mod3, mod4};
 9566
 9567                fn fn_1(param1: bool, param2: &str) {
 9568                    let var1 = "h«elˇ»lo world";
 9569                }
 9570            "#},
 9571            cx,
 9572        );
 9573        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9574        assert_text_with_selections(
 9575            editor,
 9576            indoc! {r#"
 9577                use mod1::mod2::{mod3, mod4};
 9578
 9579                fn fn_1(param1: bool, param2: &str) {
 9580                    let var1 = "«ˇhello» world";
 9581                }
 9582            "#},
 9583            cx,
 9584        );
 9585    });
 9586
 9587    // Test 3: Complete word already selected
 9588    editor.update_in(cx, |editor, window, cx| {
 9589        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9590            s.select_display_ranges([
 9591                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9592            ]);
 9593        });
 9594    });
 9595    editor.update_in(cx, |editor, window, cx| {
 9596        assert_text_with_selections(
 9597            editor,
 9598            indoc! {r#"
 9599                use mod1::mod2::{mod3, mod4};
 9600
 9601                fn fn_1(param1: bool, param2: &str) {
 9602                    let var1 = "«helloˇ» world";
 9603                }
 9604            "#},
 9605            cx,
 9606        );
 9607        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9608        assert_text_with_selections(
 9609            editor,
 9610            indoc! {r#"
 9611                use mod1::mod2::{mod3, mod4};
 9612
 9613                fn fn_1(param1: bool, param2: &str) {
 9614                    let var1 = "«hello worldˇ»";
 9615                }
 9616            "#},
 9617            cx,
 9618        );
 9619    });
 9620
 9621    // Test 4: Selection spanning across words
 9622    editor.update_in(cx, |editor, window, cx| {
 9623        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9624            s.select_display_ranges([
 9625                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9626            ]);
 9627        });
 9628    });
 9629    editor.update_in(cx, |editor, window, cx| {
 9630        assert_text_with_selections(
 9631            editor,
 9632            indoc! {r#"
 9633                use mod1::mod2::{mod3, mod4};
 9634
 9635                fn fn_1(param1: bool, param2: &str) {
 9636                    let var1 = "hel«lo woˇ»rld";
 9637                }
 9638            "#},
 9639            cx,
 9640        );
 9641        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9642        assert_text_with_selections(
 9643            editor,
 9644            indoc! {r#"
 9645                use mod1::mod2::{mod3, mod4};
 9646
 9647                fn fn_1(param1: bool, param2: &str) {
 9648                    let var1 = "«ˇhello world»";
 9649                }
 9650            "#},
 9651            cx,
 9652        );
 9653    });
 9654
 9655    // Test 5: Expansion beyond string
 9656    editor.update_in(cx, |editor, window, cx| {
 9657        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9658        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9659        assert_text_with_selections(
 9660            editor,
 9661            indoc! {r#"
 9662                use mod1::mod2::{mod3, mod4};
 9663
 9664                fn fn_1(param1: bool, param2: &str) {
 9665                    «ˇlet var1 = "hello world";»
 9666                }
 9667            "#},
 9668            cx,
 9669        );
 9670    });
 9671}
 9672
 9673#[gpui::test]
 9674async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9675    init_test(cx, |_| {});
 9676
 9677    let mut cx = EditorTestContext::new(cx).await;
 9678
 9679    let language = Arc::new(Language::new(
 9680        LanguageConfig::default(),
 9681        Some(tree_sitter_rust::LANGUAGE.into()),
 9682    ));
 9683
 9684    cx.update_buffer(|buffer, cx| {
 9685        buffer.set_language(Some(language), cx);
 9686    });
 9687
 9688    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9689    cx.update_editor(|editor, window, cx| {
 9690        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9691    });
 9692
 9693    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9694
 9695    cx.set_state(indoc! { r#"fn a() {
 9696          // what
 9697          // a
 9698          // ˇlong
 9699          // method
 9700          // I
 9701          // sure
 9702          // hope
 9703          // it
 9704          // works
 9705    }"# });
 9706
 9707    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9708    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9709    cx.update(|_, cx| {
 9710        multi_buffer.update(cx, |multi_buffer, cx| {
 9711            multi_buffer.set_excerpts_for_path(
 9712                PathKey::for_buffer(&buffer, cx),
 9713                buffer,
 9714                [Point::new(1, 0)..Point::new(1, 0)],
 9715                3,
 9716                cx,
 9717            );
 9718        });
 9719    });
 9720
 9721    let editor2 = cx.new_window_entity(|window, cx| {
 9722        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9723    });
 9724
 9725    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9726    cx.update_editor(|editor, window, cx| {
 9727        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9728            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9729        })
 9730    });
 9731
 9732    cx.assert_editor_state(indoc! { "
 9733        fn a() {
 9734              // what
 9735              // a
 9736        ˇ      // long
 9737              // method"});
 9738
 9739    cx.update_editor(|editor, window, cx| {
 9740        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9741    });
 9742
 9743    // Although we could potentially make the action work when the syntax node
 9744    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9745    // did. Maybe we could also expand the excerpt to contain the range?
 9746    cx.assert_editor_state(indoc! { "
 9747        fn a() {
 9748              // what
 9749              // a
 9750        ˇ      // long
 9751              // method"});
 9752}
 9753
 9754#[gpui::test]
 9755async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9756    init_test(cx, |_| {});
 9757
 9758    let base_text = r#"
 9759        impl A {
 9760            // this is an uncommitted comment
 9761
 9762            fn b() {
 9763                c();
 9764            }
 9765
 9766            // this is another uncommitted comment
 9767
 9768            fn d() {
 9769                // e
 9770                // f
 9771            }
 9772        }
 9773
 9774        fn g() {
 9775            // h
 9776        }
 9777    "#
 9778    .unindent();
 9779
 9780    let text = r#"
 9781        ˇimpl A {
 9782
 9783            fn b() {
 9784                c();
 9785            }
 9786
 9787            fn d() {
 9788                // e
 9789                // f
 9790            }
 9791        }
 9792
 9793        fn g() {
 9794            // h
 9795        }
 9796    "#
 9797    .unindent();
 9798
 9799    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9800    cx.set_state(&text);
 9801    cx.set_head_text(&base_text);
 9802    cx.update_editor(|editor, window, cx| {
 9803        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9804    });
 9805
 9806    cx.assert_state_with_diff(
 9807        "
 9808        ˇimpl A {
 9809      -     // this is an uncommitted comment
 9810
 9811            fn b() {
 9812                c();
 9813            }
 9814
 9815      -     // this is another uncommitted comment
 9816      -
 9817            fn d() {
 9818                // e
 9819                // f
 9820            }
 9821        }
 9822
 9823        fn g() {
 9824            // h
 9825        }
 9826    "
 9827        .unindent(),
 9828    );
 9829
 9830    let expected_display_text = "
 9831        impl A {
 9832            // this is an uncommitted comment
 9833
 9834            fn b() {
 9835 9836            }
 9837
 9838            // this is another uncommitted comment
 9839
 9840            fn d() {
 9841 9842            }
 9843        }
 9844
 9845        fn g() {
 9846 9847        }
 9848        "
 9849    .unindent();
 9850
 9851    cx.update_editor(|editor, window, cx| {
 9852        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9853        assert_eq!(editor.display_text(cx), expected_display_text);
 9854    });
 9855}
 9856
 9857#[gpui::test]
 9858async fn test_autoindent(cx: &mut TestAppContext) {
 9859    init_test(cx, |_| {});
 9860
 9861    let language = Arc::new(
 9862        Language::new(
 9863            LanguageConfig {
 9864                brackets: BracketPairConfig {
 9865                    pairs: vec![
 9866                        BracketPair {
 9867                            start: "{".to_string(),
 9868                            end: "}".to_string(),
 9869                            close: false,
 9870                            surround: false,
 9871                            newline: true,
 9872                        },
 9873                        BracketPair {
 9874                            start: "(".to_string(),
 9875                            end: ")".to_string(),
 9876                            close: false,
 9877                            surround: false,
 9878                            newline: true,
 9879                        },
 9880                    ],
 9881                    ..Default::default()
 9882                },
 9883                ..Default::default()
 9884            },
 9885            Some(tree_sitter_rust::LANGUAGE.into()),
 9886        )
 9887        .with_indents_query(
 9888            r#"
 9889                (_ "(" ")" @end) @indent
 9890                (_ "{" "}" @end) @indent
 9891            "#,
 9892        )
 9893        .unwrap(),
 9894    );
 9895
 9896    let text = "fn a() {}";
 9897
 9898    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9899    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9900    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9901    editor
 9902        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9903        .await;
 9904
 9905    editor.update_in(cx, |editor, window, cx| {
 9906        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9907            s.select_ranges([
 9908                MultiBufferOffset(5)..MultiBufferOffset(5),
 9909                MultiBufferOffset(8)..MultiBufferOffset(8),
 9910                MultiBufferOffset(9)..MultiBufferOffset(9),
 9911            ])
 9912        });
 9913        editor.newline(&Newline, window, cx);
 9914        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9915        assert_eq!(
 9916            editor.selections.ranges(&editor.display_snapshot(cx)),
 9917            &[
 9918                Point::new(1, 4)..Point::new(1, 4),
 9919                Point::new(3, 4)..Point::new(3, 4),
 9920                Point::new(5, 0)..Point::new(5, 0)
 9921            ]
 9922        );
 9923    });
 9924}
 9925
 9926#[gpui::test]
 9927async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9928    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9929
 9930    let language = Arc::new(
 9931        Language::new(
 9932            LanguageConfig {
 9933                brackets: BracketPairConfig {
 9934                    pairs: vec![
 9935                        BracketPair {
 9936                            start: "{".to_string(),
 9937                            end: "}".to_string(),
 9938                            close: false,
 9939                            surround: false,
 9940                            newline: true,
 9941                        },
 9942                        BracketPair {
 9943                            start: "(".to_string(),
 9944                            end: ")".to_string(),
 9945                            close: false,
 9946                            surround: false,
 9947                            newline: true,
 9948                        },
 9949                    ],
 9950                    ..Default::default()
 9951                },
 9952                ..Default::default()
 9953            },
 9954            Some(tree_sitter_rust::LANGUAGE.into()),
 9955        )
 9956        .with_indents_query(
 9957            r#"
 9958                (_ "(" ")" @end) @indent
 9959                (_ "{" "}" @end) @indent
 9960            "#,
 9961        )
 9962        .unwrap(),
 9963    );
 9964
 9965    let text = "fn a() {}";
 9966
 9967    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9968    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9969    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9970    editor
 9971        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9972        .await;
 9973
 9974    editor.update_in(cx, |editor, window, cx| {
 9975        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9976            s.select_ranges([
 9977                MultiBufferOffset(5)..MultiBufferOffset(5),
 9978                MultiBufferOffset(8)..MultiBufferOffset(8),
 9979                MultiBufferOffset(9)..MultiBufferOffset(9),
 9980            ])
 9981        });
 9982        editor.newline(&Newline, window, cx);
 9983        assert_eq!(
 9984            editor.text(cx),
 9985            indoc!(
 9986                "
 9987                fn a(
 9988
 9989                ) {
 9990
 9991                }
 9992                "
 9993            )
 9994        );
 9995        assert_eq!(
 9996            editor.selections.ranges(&editor.display_snapshot(cx)),
 9997            &[
 9998                Point::new(1, 0)..Point::new(1, 0),
 9999                Point::new(3, 0)..Point::new(3, 0),
10000                Point::new(5, 0)..Point::new(5, 0)
10001            ]
10002        );
10003    });
10004}
10005
10006#[gpui::test]
10007async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
10008    init_test(cx, |settings| {
10009        settings.defaults.auto_indent = Some(true);
10010        settings.languages.0.insert(
10011            "python".into(),
10012            LanguageSettingsContent {
10013                auto_indent: Some(false),
10014                ..Default::default()
10015            },
10016        );
10017    });
10018
10019    let mut cx = EditorTestContext::new(cx).await;
10020
10021    let injected_language = Arc::new(
10022        Language::new(
10023            LanguageConfig {
10024                brackets: BracketPairConfig {
10025                    pairs: vec![
10026                        BracketPair {
10027                            start: "{".to_string(),
10028                            end: "}".to_string(),
10029                            close: false,
10030                            surround: false,
10031                            newline: true,
10032                        },
10033                        BracketPair {
10034                            start: "(".to_string(),
10035                            end: ")".to_string(),
10036                            close: true,
10037                            surround: false,
10038                            newline: true,
10039                        },
10040                    ],
10041                    ..Default::default()
10042                },
10043                name: "python".into(),
10044                ..Default::default()
10045            },
10046            Some(tree_sitter_python::LANGUAGE.into()),
10047        )
10048        .with_indents_query(
10049            r#"
10050                (_ "(" ")" @end) @indent
10051                (_ "{" "}" @end) @indent
10052            "#,
10053        )
10054        .unwrap(),
10055    );
10056
10057    let language = Arc::new(
10058        Language::new(
10059            LanguageConfig {
10060                brackets: BracketPairConfig {
10061                    pairs: vec![
10062                        BracketPair {
10063                            start: "{".to_string(),
10064                            end: "}".to_string(),
10065                            close: false,
10066                            surround: false,
10067                            newline: true,
10068                        },
10069                        BracketPair {
10070                            start: "(".to_string(),
10071                            end: ")".to_string(),
10072                            close: true,
10073                            surround: false,
10074                            newline: true,
10075                        },
10076                    ],
10077                    ..Default::default()
10078                },
10079                name: LanguageName::new_static("rust"),
10080                ..Default::default()
10081            },
10082            Some(tree_sitter_rust::LANGUAGE.into()),
10083        )
10084        .with_indents_query(
10085            r#"
10086                (_ "(" ")" @end) @indent
10087                (_ "{" "}" @end) @indent
10088            "#,
10089        )
10090        .unwrap()
10091        .with_injection_query(
10092            r#"
10093            (macro_invocation
10094                macro: (identifier) @_macro_name
10095                (token_tree) @injection.content
10096                (#set! injection.language "python"))
10097           "#,
10098        )
10099        .unwrap(),
10100    );
10101
10102    cx.language_registry().add(injected_language);
10103    cx.language_registry().add(language.clone());
10104
10105    cx.update_buffer(|buffer, cx| {
10106        buffer.set_language(Some(language), cx);
10107    });
10108
10109    cx.set_state(r#"struct A {ˇ}"#);
10110
10111    cx.update_editor(|editor, window, cx| {
10112        editor.newline(&Default::default(), window, cx);
10113    });
10114
10115    cx.assert_editor_state(indoc!(
10116        "struct A {
10117            ˇ
10118        }"
10119    ));
10120
10121    cx.set_state(r#"select_biased!(ˇ)"#);
10122
10123    cx.update_editor(|editor, window, cx| {
10124        editor.newline(&Default::default(), window, cx);
10125        editor.handle_input("def ", window, cx);
10126        editor.handle_input("(", window, cx);
10127        editor.newline(&Default::default(), window, cx);
10128        editor.handle_input("a", window, cx);
10129    });
10130
10131    cx.assert_editor_state(indoc!(
10132        "select_biased!(
10133        def (
1013410135        )
10136        )"
10137    ));
10138}
10139
10140#[gpui::test]
10141async fn test_autoindent_selections(cx: &mut TestAppContext) {
10142    init_test(cx, |_| {});
10143
10144    {
10145        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10146        cx.set_state(indoc! {"
10147            impl A {
10148
10149                fn b() {}
10150
10151            «fn c() {
10152
10153            }ˇ»
10154            }
10155        "});
10156
10157        cx.update_editor(|editor, window, cx| {
10158            editor.autoindent(&Default::default(), window, cx);
10159        });
10160
10161        cx.assert_editor_state(indoc! {"
10162            impl A {
10163
10164                fn b() {}
10165
10166                «fn c() {
10167
10168                }ˇ»
10169            }
10170        "});
10171    }
10172
10173    {
10174        let mut cx = EditorTestContext::new_multibuffer(
10175            cx,
10176            [indoc! { "
10177                impl A {
10178                «
10179                // a
10180                fn b(){}
10181                »
10182                «
10183                    }
10184                    fn c(){}
10185                »
10186            "}],
10187        );
10188
10189        let buffer = cx.update_editor(|editor, _, cx| {
10190            let buffer = editor.buffer().update(cx, |buffer, _| {
10191                buffer.all_buffers().iter().next().unwrap().clone()
10192            });
10193            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10194            buffer
10195        });
10196
10197        cx.run_until_parked();
10198        cx.update_editor(|editor, window, cx| {
10199            editor.select_all(&Default::default(), window, cx);
10200            editor.autoindent(&Default::default(), window, cx)
10201        });
10202        cx.run_until_parked();
10203
10204        cx.update(|_, cx| {
10205            assert_eq!(
10206                buffer.read(cx).text(),
10207                indoc! { "
10208                    impl A {
10209
10210                        // a
10211                        fn b(){}
10212
10213
10214                    }
10215                    fn c(){}
10216
10217                " }
10218            )
10219        });
10220    }
10221}
10222
10223#[gpui::test]
10224async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10225    init_test(cx, |_| {});
10226
10227    let mut cx = EditorTestContext::new(cx).await;
10228
10229    let language = Arc::new(Language::new(
10230        LanguageConfig {
10231            brackets: BracketPairConfig {
10232                pairs: vec![
10233                    BracketPair {
10234                        start: "{".to_string(),
10235                        end: "}".to_string(),
10236                        close: true,
10237                        surround: true,
10238                        newline: true,
10239                    },
10240                    BracketPair {
10241                        start: "(".to_string(),
10242                        end: ")".to_string(),
10243                        close: true,
10244                        surround: true,
10245                        newline: true,
10246                    },
10247                    BracketPair {
10248                        start: "/*".to_string(),
10249                        end: " */".to_string(),
10250                        close: true,
10251                        surround: true,
10252                        newline: true,
10253                    },
10254                    BracketPair {
10255                        start: "[".to_string(),
10256                        end: "]".to_string(),
10257                        close: false,
10258                        surround: false,
10259                        newline: true,
10260                    },
10261                    BracketPair {
10262                        start: "\"".to_string(),
10263                        end: "\"".to_string(),
10264                        close: true,
10265                        surround: true,
10266                        newline: false,
10267                    },
10268                    BracketPair {
10269                        start: "<".to_string(),
10270                        end: ">".to_string(),
10271                        close: false,
10272                        surround: true,
10273                        newline: true,
10274                    },
10275                ],
10276                ..Default::default()
10277            },
10278            autoclose_before: "})]".to_string(),
10279            ..Default::default()
10280        },
10281        Some(tree_sitter_rust::LANGUAGE.into()),
10282    ));
10283
10284    cx.language_registry().add(language.clone());
10285    cx.update_buffer(|buffer, cx| {
10286        buffer.set_language(Some(language), cx);
10287    });
10288
10289    cx.set_state(
10290        &r#"
10291            🏀ˇ
10292            εˇ
10293            ❤️ˇ
10294        "#
10295        .unindent(),
10296    );
10297
10298    // autoclose multiple nested brackets at multiple cursors
10299    cx.update_editor(|editor, window, cx| {
10300        editor.handle_input("{", window, cx);
10301        editor.handle_input("{", window, cx);
10302        editor.handle_input("{", window, cx);
10303    });
10304    cx.assert_editor_state(
10305        &"
10306            🏀{{{ˇ}}}
10307            ε{{{ˇ}}}
10308            ❤️{{{ˇ}}}
10309        "
10310        .unindent(),
10311    );
10312
10313    // insert a different closing bracket
10314    cx.update_editor(|editor, window, cx| {
10315        editor.handle_input(")", window, cx);
10316    });
10317    cx.assert_editor_state(
10318        &"
10319            🏀{{{)ˇ}}}
10320            ε{{{)ˇ}}}
10321            ❤️{{{)ˇ}}}
10322        "
10323        .unindent(),
10324    );
10325
10326    // skip over the auto-closed brackets when typing a closing bracket
10327    cx.update_editor(|editor, window, cx| {
10328        editor.move_right(&MoveRight, window, cx);
10329        editor.handle_input("}", window, cx);
10330        editor.handle_input("}", window, cx);
10331        editor.handle_input("}", window, cx);
10332    });
10333    cx.assert_editor_state(
10334        &"
10335            🏀{{{)}}}}ˇ
10336            ε{{{)}}}}ˇ
10337            ❤️{{{)}}}}ˇ
10338        "
10339        .unindent(),
10340    );
10341
10342    // autoclose multi-character pairs
10343    cx.set_state(
10344        &"
10345            ˇ
10346            ˇ
10347        "
10348        .unindent(),
10349    );
10350    cx.update_editor(|editor, window, cx| {
10351        editor.handle_input("/", window, cx);
10352        editor.handle_input("*", window, cx);
10353    });
10354    cx.assert_editor_state(
10355        &"
10356            /*ˇ */
10357            /*ˇ */
10358        "
10359        .unindent(),
10360    );
10361
10362    // one cursor autocloses a multi-character pair, one cursor
10363    // does not autoclose.
10364    cx.set_state(
10365        &"
1036610367            ˇ
10368        "
10369        .unindent(),
10370    );
10371    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10372    cx.assert_editor_state(
10373        &"
10374            /*ˇ */
1037510376        "
10377        .unindent(),
10378    );
10379
10380    // Don't autoclose if the next character isn't whitespace and isn't
10381    // listed in the language's "autoclose_before" section.
10382    cx.set_state("ˇa b");
10383    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10384    cx.assert_editor_state("{ˇa b");
10385
10386    // Don't autoclose if `close` is false for the bracket pair
10387    cx.set_state("ˇ");
10388    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10389    cx.assert_editor_state("");
10390
10391    // Surround with brackets if text is selected
10392    cx.set_state("«aˇ» b");
10393    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10394    cx.assert_editor_state("{«aˇ»} b");
10395
10396    // Autoclose when not immediately after a word character
10397    cx.set_state("a ˇ");
10398    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10399    cx.assert_editor_state("a \"ˇ\"");
10400
10401    // Autoclose pair where the start and end characters are the same
10402    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10403    cx.assert_editor_state("a \"\"ˇ");
10404
10405    // Don't autoclose when immediately after a word character
10406    cx.set_state("");
10407    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10408    cx.assert_editor_state("a\"ˇ");
10409
10410    // Do autoclose when after a non-word character
10411    cx.set_state("");
10412    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10413    cx.assert_editor_state("{\"ˇ\"");
10414
10415    // Non identical pairs autoclose regardless of preceding character
10416    cx.set_state("");
10417    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10418    cx.assert_editor_state("a{ˇ}");
10419
10420    // Don't autoclose pair if autoclose is disabled
10421    cx.set_state("ˇ");
10422    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10423    cx.assert_editor_state("");
10424
10425    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10426    cx.set_state("«aˇ» b");
10427    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10428    cx.assert_editor_state("<«aˇ»> b");
10429}
10430
10431#[gpui::test]
10432async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10433    init_test(cx, |settings| {
10434        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10435    });
10436
10437    let mut cx = EditorTestContext::new(cx).await;
10438
10439    let language = Arc::new(Language::new(
10440        LanguageConfig {
10441            brackets: BracketPairConfig {
10442                pairs: vec![
10443                    BracketPair {
10444                        start: "{".to_string(),
10445                        end: "}".to_string(),
10446                        close: true,
10447                        surround: true,
10448                        newline: true,
10449                    },
10450                    BracketPair {
10451                        start: "(".to_string(),
10452                        end: ")".to_string(),
10453                        close: true,
10454                        surround: true,
10455                        newline: true,
10456                    },
10457                    BracketPair {
10458                        start: "[".to_string(),
10459                        end: "]".to_string(),
10460                        close: false,
10461                        surround: false,
10462                        newline: true,
10463                    },
10464                ],
10465                ..Default::default()
10466            },
10467            autoclose_before: "})]".to_string(),
10468            ..Default::default()
10469        },
10470        Some(tree_sitter_rust::LANGUAGE.into()),
10471    ));
10472
10473    cx.language_registry().add(language.clone());
10474    cx.update_buffer(|buffer, cx| {
10475        buffer.set_language(Some(language), cx);
10476    });
10477
10478    cx.set_state(
10479        &"
10480            ˇ
10481            ˇ
10482            ˇ
10483        "
10484        .unindent(),
10485    );
10486
10487    // ensure only matching closing brackets are skipped over
10488    cx.update_editor(|editor, window, cx| {
10489        editor.handle_input("}", window, cx);
10490        editor.move_left(&MoveLeft, window, cx);
10491        editor.handle_input(")", window, cx);
10492        editor.move_left(&MoveLeft, window, cx);
10493    });
10494    cx.assert_editor_state(
10495        &"
10496            ˇ)}
10497            ˇ)}
10498            ˇ)}
10499        "
10500        .unindent(),
10501    );
10502
10503    // skip-over closing brackets at multiple cursors
10504    cx.update_editor(|editor, window, cx| {
10505        editor.handle_input(")", window, cx);
10506        editor.handle_input("}", window, cx);
10507    });
10508    cx.assert_editor_state(
10509        &"
10510            )}ˇ
10511            )}ˇ
10512            )}ˇ
10513        "
10514        .unindent(),
10515    );
10516
10517    // ignore non-close brackets
10518    cx.update_editor(|editor, window, cx| {
10519        editor.handle_input("]", window, cx);
10520        editor.move_left(&MoveLeft, window, cx);
10521        editor.handle_input("]", window, cx);
10522    });
10523    cx.assert_editor_state(
10524        &"
10525            )}]ˇ]
10526            )}]ˇ]
10527            )}]ˇ]
10528        "
10529        .unindent(),
10530    );
10531}
10532
10533#[gpui::test]
10534async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10535    init_test(cx, |_| {});
10536
10537    let mut cx = EditorTestContext::new(cx).await;
10538
10539    let html_language = Arc::new(
10540        Language::new(
10541            LanguageConfig {
10542                name: "HTML".into(),
10543                brackets: BracketPairConfig {
10544                    pairs: vec![
10545                        BracketPair {
10546                            start: "<".into(),
10547                            end: ">".into(),
10548                            close: true,
10549                            ..Default::default()
10550                        },
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                    ],
10564                    ..Default::default()
10565                },
10566                autoclose_before: "})]>".into(),
10567                ..Default::default()
10568            },
10569            Some(tree_sitter_html::LANGUAGE.into()),
10570        )
10571        .with_injection_query(
10572            r#"
10573            (script_element
10574                (raw_text) @injection.content
10575                (#set! injection.language "javascript"))
10576            "#,
10577        )
10578        .unwrap(),
10579    );
10580
10581    let javascript_language = Arc::new(Language::new(
10582        LanguageConfig {
10583            name: "JavaScript".into(),
10584            brackets: BracketPairConfig {
10585                pairs: vec![
10586                    BracketPair {
10587                        start: "/*".into(),
10588                        end: " */".into(),
10589                        close: true,
10590                        ..Default::default()
10591                    },
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                ],
10605                ..Default::default()
10606            },
10607            autoclose_before: "})]>".into(),
10608            ..Default::default()
10609        },
10610        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10611    ));
10612
10613    cx.language_registry().add(html_language.clone());
10614    cx.language_registry().add(javascript_language);
10615    cx.executor().run_until_parked();
10616
10617    cx.update_buffer(|buffer, cx| {
10618        buffer.set_language(Some(html_language), cx);
10619    });
10620
10621    cx.set_state(
10622        &r#"
10623            <body>ˇ
10624                <script>
10625                    var x = 1;ˇ
10626                </script>
10627            </body>ˇ
10628        "#
10629        .unindent(),
10630    );
10631
10632    // Precondition: different languages are active at different locations.
10633    cx.update_editor(|editor, window, cx| {
10634        let snapshot = editor.snapshot(window, cx);
10635        let cursors = editor
10636            .selections
10637            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10638        let languages = cursors
10639            .iter()
10640            .map(|c| snapshot.language_at(c.start).unwrap().name())
10641            .collect::<Vec<_>>();
10642        assert_eq!(
10643            languages,
10644            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10645        );
10646    });
10647
10648    // Angle brackets autoclose in HTML, but not JavaScript.
10649    cx.update_editor(|editor, window, cx| {
10650        editor.handle_input("<", window, cx);
10651        editor.handle_input("a", window, cx);
10652    });
10653    cx.assert_editor_state(
10654        &r#"
10655            <body><aˇ>
10656                <script>
10657                    var x = 1;<aˇ
10658                </script>
10659            </body><aˇ>
10660        "#
10661        .unindent(),
10662    );
10663
10664    // Curly braces and parens autoclose in both HTML and JavaScript.
10665    cx.update_editor(|editor, window, cx| {
10666        editor.handle_input(" b=", window, cx);
10667        editor.handle_input("{", window, cx);
10668        editor.handle_input("c", window, cx);
10669        editor.handle_input("(", window, cx);
10670    });
10671    cx.assert_editor_state(
10672        &r#"
10673            <body><a b={c(ˇ)}>
10674                <script>
10675                    var x = 1;<a b={c(ˇ)}
10676                </script>
10677            </body><a b={c(ˇ)}>
10678        "#
10679        .unindent(),
10680    );
10681
10682    // Brackets that were already autoclosed are skipped.
10683    cx.update_editor(|editor, window, cx| {
10684        editor.handle_input(")", window, cx);
10685        editor.handle_input("d", window, cx);
10686        editor.handle_input("}", window, cx);
10687    });
10688    cx.assert_editor_state(
10689        &r#"
10690            <body><a b={c()d}ˇ>
10691                <script>
10692                    var x = 1;<a b={c()d}ˇ
10693                </script>
10694            </body><a b={c()d}ˇ>
10695        "#
10696        .unindent(),
10697    );
10698    cx.update_editor(|editor, window, cx| {
10699        editor.handle_input(">", window, cx);
10700    });
10701    cx.assert_editor_state(
10702        &r#"
10703            <body><a b={c()d}>ˇ
10704                <script>
10705                    var x = 1;<a b={c()d}>ˇ
10706                </script>
10707            </body><a b={c()d}>ˇ
10708        "#
10709        .unindent(),
10710    );
10711
10712    // Reset
10713    cx.set_state(
10714        &r#"
10715            <body>ˇ
10716                <script>
10717                    var x = 1;ˇ
10718                </script>
10719            </body>ˇ
10720        "#
10721        .unindent(),
10722    );
10723
10724    cx.update_editor(|editor, window, cx| {
10725        editor.handle_input("<", window, cx);
10726    });
10727    cx.assert_editor_state(
10728        &r#"
10729            <body><ˇ>
10730                <script>
10731                    var x = 1;<ˇ
10732                </script>
10733            </body><ˇ>
10734        "#
10735        .unindent(),
10736    );
10737
10738    // When backspacing, the closing angle brackets are removed.
10739    cx.update_editor(|editor, window, cx| {
10740        editor.backspace(&Backspace, window, cx);
10741    });
10742    cx.assert_editor_state(
10743        &r#"
10744            <body>ˇ
10745                <script>
10746                    var x = 1;ˇ
10747                </script>
10748            </body>ˇ
10749        "#
10750        .unindent(),
10751    );
10752
10753    // Block comments autoclose in JavaScript, but not HTML.
10754    cx.update_editor(|editor, window, cx| {
10755        editor.handle_input("/", window, cx);
10756        editor.handle_input("*", window, cx);
10757    });
10758    cx.assert_editor_state(
10759        &r#"
10760            <body>/*ˇ
10761                <script>
10762                    var x = 1;/*ˇ */
10763                </script>
10764            </body>/*ˇ
10765        "#
10766        .unindent(),
10767    );
10768}
10769
10770#[gpui::test]
10771async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10772    init_test(cx, |_| {});
10773
10774    let mut cx = EditorTestContext::new(cx).await;
10775
10776    let rust_language = Arc::new(
10777        Language::new(
10778            LanguageConfig {
10779                name: "Rust".into(),
10780                brackets: serde_json::from_value(json!([
10781                    { "start": "{", "end": "}", "close": true, "newline": true },
10782                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10783                ]))
10784                .unwrap(),
10785                autoclose_before: "})]>".into(),
10786                ..Default::default()
10787            },
10788            Some(tree_sitter_rust::LANGUAGE.into()),
10789        )
10790        .with_override_query("(string_literal) @string")
10791        .unwrap(),
10792    );
10793
10794    cx.language_registry().add(rust_language.clone());
10795    cx.update_buffer(|buffer, cx| {
10796        buffer.set_language(Some(rust_language), cx);
10797    });
10798
10799    cx.set_state(
10800        &r#"
10801            let x = ˇ
10802        "#
10803        .unindent(),
10804    );
10805
10806    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10807    cx.update_editor(|editor, window, cx| {
10808        editor.handle_input("\"", window, cx);
10809    });
10810    cx.assert_editor_state(
10811        &r#"
10812            let x = "ˇ"
10813        "#
10814        .unindent(),
10815    );
10816
10817    // Inserting another quotation mark. The cursor moves across the existing
10818    // automatically-inserted quotation mark.
10819    cx.update_editor(|editor, window, cx| {
10820        editor.handle_input("\"", window, cx);
10821    });
10822    cx.assert_editor_state(
10823        &r#"
10824            let x = ""ˇ
10825        "#
10826        .unindent(),
10827    );
10828
10829    // Reset
10830    cx.set_state(
10831        &r#"
10832            let x = ˇ
10833        "#
10834        .unindent(),
10835    );
10836
10837    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10838    cx.update_editor(|editor, window, cx| {
10839        editor.handle_input("\"", window, cx);
10840        editor.handle_input(" ", window, cx);
10841        editor.move_left(&Default::default(), window, cx);
10842        editor.handle_input("\\", window, cx);
10843        editor.handle_input("\"", window, cx);
10844    });
10845    cx.assert_editor_state(
10846        &r#"
10847            let x = "\"ˇ "
10848        "#
10849        .unindent(),
10850    );
10851
10852    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10853    // mark. Nothing is inserted.
10854    cx.update_editor(|editor, window, cx| {
10855        editor.move_right(&Default::default(), window, cx);
10856        editor.handle_input("\"", window, cx);
10857    });
10858    cx.assert_editor_state(
10859        &r#"
10860            let x = "\" "ˇ
10861        "#
10862        .unindent(),
10863    );
10864}
10865
10866#[gpui::test]
10867async fn test_surround_with_pair(cx: &mut TestAppContext) {
10868    init_test(cx, |_| {});
10869
10870    let language = Arc::new(Language::new(
10871        LanguageConfig {
10872            brackets: BracketPairConfig {
10873                pairs: vec![
10874                    BracketPair {
10875                        start: "{".to_string(),
10876                        end: "}".to_string(),
10877                        close: true,
10878                        surround: true,
10879                        newline: true,
10880                    },
10881                    BracketPair {
10882                        start: "/* ".to_string(),
10883                        end: "*/".to_string(),
10884                        close: true,
10885                        surround: true,
10886                        ..Default::default()
10887                    },
10888                ],
10889                ..Default::default()
10890            },
10891            ..Default::default()
10892        },
10893        Some(tree_sitter_rust::LANGUAGE.into()),
10894    ));
10895
10896    let text = r#"
10897        a
10898        b
10899        c
10900    "#
10901    .unindent();
10902
10903    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10904    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10905    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10906    editor
10907        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10908        .await;
10909
10910    editor.update_in(cx, |editor, window, cx| {
10911        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10912            s.select_display_ranges([
10913                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10914                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10915                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10916            ])
10917        });
10918
10919        editor.handle_input("{", window, cx);
10920        editor.handle_input("{", window, cx);
10921        editor.handle_input("{", window, cx);
10922        assert_eq!(
10923            editor.text(cx),
10924            "
10925                {{{a}}}
10926                {{{b}}}
10927                {{{c}}}
10928            "
10929            .unindent()
10930        );
10931        assert_eq!(
10932            display_ranges(editor, cx),
10933            [
10934                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10935                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10936                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10937            ]
10938        );
10939
10940        editor.undo(&Undo, window, cx);
10941        editor.undo(&Undo, window, cx);
10942        editor.undo(&Undo, window, cx);
10943        assert_eq!(
10944            editor.text(cx),
10945            "
10946                a
10947                b
10948                c
10949            "
10950            .unindent()
10951        );
10952        assert_eq!(
10953            display_ranges(editor, cx),
10954            [
10955                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10956                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10957                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10958            ]
10959        );
10960
10961        // Ensure inserting the first character of a multi-byte bracket pair
10962        // doesn't surround the selections with the bracket.
10963        editor.handle_input("/", window, cx);
10964        assert_eq!(
10965            editor.text(cx),
10966            "
10967                /
10968                /
10969                /
10970            "
10971            .unindent()
10972        );
10973        assert_eq!(
10974            display_ranges(editor, cx),
10975            [
10976                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10977                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10978                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10979            ]
10980        );
10981
10982        editor.undo(&Undo, window, cx);
10983        assert_eq!(
10984            editor.text(cx),
10985            "
10986                a
10987                b
10988                c
10989            "
10990            .unindent()
10991        );
10992        assert_eq!(
10993            display_ranges(editor, cx),
10994            [
10995                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10996                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10997                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10998            ]
10999        );
11000
11001        // Ensure inserting the last character of a multi-byte bracket pair
11002        // doesn't surround the selections with the bracket.
11003        editor.handle_input("*", window, cx);
11004        assert_eq!(
11005            editor.text(cx),
11006            "
11007                *
11008                *
11009                *
11010            "
11011            .unindent()
11012        );
11013        assert_eq!(
11014            display_ranges(editor, cx),
11015            [
11016                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
11017                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
11018                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
11019            ]
11020        );
11021    });
11022}
11023
11024#[gpui::test]
11025async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
11026    init_test(cx, |_| {});
11027
11028    let language = Arc::new(Language::new(
11029        LanguageConfig {
11030            brackets: BracketPairConfig {
11031                pairs: vec![BracketPair {
11032                    start: "{".to_string(),
11033                    end: "}".to_string(),
11034                    close: true,
11035                    surround: true,
11036                    newline: true,
11037                }],
11038                ..Default::default()
11039            },
11040            autoclose_before: "}".to_string(),
11041            ..Default::default()
11042        },
11043        Some(tree_sitter_rust::LANGUAGE.into()),
11044    ));
11045
11046    let text = r#"
11047        a
11048        b
11049        c
11050    "#
11051    .unindent();
11052
11053    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
11054    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11055    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11056    editor
11057        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11058        .await;
11059
11060    editor.update_in(cx, |editor, window, cx| {
11061        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11062            s.select_ranges([
11063                Point::new(0, 1)..Point::new(0, 1),
11064                Point::new(1, 1)..Point::new(1, 1),
11065                Point::new(2, 1)..Point::new(2, 1),
11066            ])
11067        });
11068
11069        editor.handle_input("{", window, cx);
11070        editor.handle_input("{", window, cx);
11071        editor.handle_input("_", window, cx);
11072        assert_eq!(
11073            editor.text(cx),
11074            "
11075                a{{_}}
11076                b{{_}}
11077                c{{_}}
11078            "
11079            .unindent()
11080        );
11081        assert_eq!(
11082            editor
11083                .selections
11084                .ranges::<Point>(&editor.display_snapshot(cx)),
11085            [
11086                Point::new(0, 4)..Point::new(0, 4),
11087                Point::new(1, 4)..Point::new(1, 4),
11088                Point::new(2, 4)..Point::new(2, 4)
11089            ]
11090        );
11091
11092        editor.backspace(&Default::default(), window, cx);
11093        editor.backspace(&Default::default(), window, cx);
11094        assert_eq!(
11095            editor.text(cx),
11096            "
11097                a{}
11098                b{}
11099                c{}
11100            "
11101            .unindent()
11102        );
11103        assert_eq!(
11104            editor
11105                .selections
11106                .ranges::<Point>(&editor.display_snapshot(cx)),
11107            [
11108                Point::new(0, 2)..Point::new(0, 2),
11109                Point::new(1, 2)..Point::new(1, 2),
11110                Point::new(2, 2)..Point::new(2, 2)
11111            ]
11112        );
11113
11114        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11115        assert_eq!(
11116            editor.text(cx),
11117            "
11118                a
11119                b
11120                c
11121            "
11122            .unindent()
11123        );
11124        assert_eq!(
11125            editor
11126                .selections
11127                .ranges::<Point>(&editor.display_snapshot(cx)),
11128            [
11129                Point::new(0, 1)..Point::new(0, 1),
11130                Point::new(1, 1)..Point::new(1, 1),
11131                Point::new(2, 1)..Point::new(2, 1)
11132            ]
11133        );
11134    });
11135}
11136
11137#[gpui::test]
11138async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11139    init_test(cx, |settings| {
11140        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11141    });
11142
11143    let mut cx = EditorTestContext::new(cx).await;
11144
11145    let language = Arc::new(Language::new(
11146        LanguageConfig {
11147            brackets: BracketPairConfig {
11148                pairs: vec![
11149                    BracketPair {
11150                        start: "{".to_string(),
11151                        end: "}".to_string(),
11152                        close: true,
11153                        surround: true,
11154                        newline: true,
11155                    },
11156                    BracketPair {
11157                        start: "(".to_string(),
11158                        end: ")".to_string(),
11159                        close: true,
11160                        surround: true,
11161                        newline: true,
11162                    },
11163                    BracketPair {
11164                        start: "[".to_string(),
11165                        end: "]".to_string(),
11166                        close: false,
11167                        surround: true,
11168                        newline: true,
11169                    },
11170                ],
11171                ..Default::default()
11172            },
11173            autoclose_before: "})]".to_string(),
11174            ..Default::default()
11175        },
11176        Some(tree_sitter_rust::LANGUAGE.into()),
11177    ));
11178
11179    cx.language_registry().add(language.clone());
11180    cx.update_buffer(|buffer, cx| {
11181        buffer.set_language(Some(language), cx);
11182    });
11183
11184    cx.set_state(
11185        &"
11186            {(ˇ)}
11187            [[ˇ]]
11188            {(ˇ)}
11189        "
11190        .unindent(),
11191    );
11192
11193    cx.update_editor(|editor, window, cx| {
11194        editor.backspace(&Default::default(), window, cx);
11195        editor.backspace(&Default::default(), window, cx);
11196    });
11197
11198    cx.assert_editor_state(
11199        &"
11200            ˇ
11201            ˇ]]
11202            ˇ
11203        "
11204        .unindent(),
11205    );
11206
11207    cx.update_editor(|editor, window, cx| {
11208        editor.handle_input("{", window, cx);
11209        editor.handle_input("{", window, cx);
11210        editor.move_right(&MoveRight, window, cx);
11211        editor.move_right(&MoveRight, window, cx);
11212        editor.move_left(&MoveLeft, window, cx);
11213        editor.move_left(&MoveLeft, window, cx);
11214        editor.backspace(&Default::default(), window, cx);
11215    });
11216
11217    cx.assert_editor_state(
11218        &"
11219            {ˇ}
11220            {ˇ}]]
11221            {ˇ}
11222        "
11223        .unindent(),
11224    );
11225
11226    cx.update_editor(|editor, window, cx| {
11227        editor.backspace(&Default::default(), window, cx);
11228    });
11229
11230    cx.assert_editor_state(
11231        &"
11232            ˇ
11233            ˇ]]
11234            ˇ
11235        "
11236        .unindent(),
11237    );
11238}
11239
11240#[gpui::test]
11241async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11242    init_test(cx, |_| {});
11243
11244    let language = Arc::new(Language::new(
11245        LanguageConfig::default(),
11246        Some(tree_sitter_rust::LANGUAGE.into()),
11247    ));
11248
11249    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11250    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11251    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11252    editor
11253        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11254        .await;
11255
11256    editor.update_in(cx, |editor, window, cx| {
11257        editor.set_auto_replace_emoji_shortcode(true);
11258
11259        editor.handle_input("Hello ", window, cx);
11260        editor.handle_input(":wave", window, cx);
11261        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11262
11263        editor.handle_input(":", window, cx);
11264        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11265
11266        editor.handle_input(" :smile", window, cx);
11267        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11268
11269        editor.handle_input(":", window, cx);
11270        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11271
11272        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11273        editor.handle_input(":wave", window, cx);
11274        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11275
11276        editor.handle_input(":", window, cx);
11277        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11278
11279        editor.handle_input(":1", window, cx);
11280        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11281
11282        editor.handle_input(":", window, cx);
11283        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11284
11285        // Ensure shortcode does not get replaced when it is part of a word
11286        editor.handle_input(" Test:wave", window, cx);
11287        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11288
11289        editor.handle_input(":", window, cx);
11290        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11291
11292        editor.set_auto_replace_emoji_shortcode(false);
11293
11294        // Ensure shortcode does not get replaced when auto replace is off
11295        editor.handle_input(" :wave", window, cx);
11296        assert_eq!(
11297            editor.text(cx),
11298            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11299        );
11300
11301        editor.handle_input(":", window, cx);
11302        assert_eq!(
11303            editor.text(cx),
11304            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11305        );
11306    });
11307}
11308
11309#[gpui::test]
11310async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11311    init_test(cx, |_| {});
11312
11313    let (text, insertion_ranges) = marked_text_ranges(
11314        indoc! {"
11315            ˇ
11316        "},
11317        false,
11318    );
11319
11320    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11321    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11322
11323    _ = editor.update_in(cx, |editor, window, cx| {
11324        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11325
11326        editor
11327            .insert_snippet(
11328                &insertion_ranges
11329                    .iter()
11330                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11331                    .collect::<Vec<_>>(),
11332                snippet,
11333                window,
11334                cx,
11335            )
11336            .unwrap();
11337
11338        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11339            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11340            assert_eq!(editor.text(cx), expected_text);
11341            assert_eq!(
11342                editor.selections.ranges(&editor.display_snapshot(cx)),
11343                selection_ranges
11344                    .iter()
11345                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11346                    .collect::<Vec<_>>()
11347            );
11348        }
11349
11350        assert(
11351            editor,
11352            cx,
11353            indoc! {"
11354            type «» =•
11355            "},
11356        );
11357
11358        assert!(editor.context_menu_visible(), "There should be a matches");
11359    });
11360}
11361
11362#[gpui::test]
11363async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11364    init_test(cx, |_| {});
11365
11366    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11367        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11368        assert_eq!(editor.text(cx), expected_text);
11369        assert_eq!(
11370            editor.selections.ranges(&editor.display_snapshot(cx)),
11371            selection_ranges
11372                .iter()
11373                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11374                .collect::<Vec<_>>()
11375        );
11376    }
11377
11378    let (text, insertion_ranges) = marked_text_ranges(
11379        indoc! {"
11380            ˇ
11381        "},
11382        false,
11383    );
11384
11385    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11386    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11387
11388    _ = editor.update_in(cx, |editor, window, cx| {
11389        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11390
11391        editor
11392            .insert_snippet(
11393                &insertion_ranges
11394                    .iter()
11395                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11396                    .collect::<Vec<_>>(),
11397                snippet,
11398                window,
11399                cx,
11400            )
11401            .unwrap();
11402
11403        assert_state(
11404            editor,
11405            cx,
11406            indoc! {"
11407            type «» = ;•
11408            "},
11409        );
11410
11411        assert!(
11412            editor.context_menu_visible(),
11413            "Context menu should be visible for placeholder choices"
11414        );
11415
11416        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11417
11418        assert_state(
11419            editor,
11420            cx,
11421            indoc! {"
11422            type  = «»;•
11423            "},
11424        );
11425
11426        assert!(
11427            !editor.context_menu_visible(),
11428            "Context menu should be hidden after moving to next tabstop"
11429        );
11430
11431        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11432
11433        assert_state(
11434            editor,
11435            cx,
11436            indoc! {"
11437            type  = ; ˇ
11438            "},
11439        );
11440
11441        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11442
11443        assert_state(
11444            editor,
11445            cx,
11446            indoc! {"
11447            type  = ; ˇ
11448            "},
11449        );
11450    });
11451
11452    _ = editor.update_in(cx, |editor, window, cx| {
11453        editor.select_all(&SelectAll, window, cx);
11454        editor.backspace(&Backspace, window, cx);
11455
11456        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11457        let insertion_ranges = editor
11458            .selections
11459            .all(&editor.display_snapshot(cx))
11460            .iter()
11461            .map(|s| s.range())
11462            .collect::<Vec<_>>();
11463
11464        editor
11465            .insert_snippet(&insertion_ranges, snippet, window, cx)
11466            .unwrap();
11467
11468        assert_state(editor, cx, "fn «» = value;•");
11469
11470        assert!(
11471            editor.context_menu_visible(),
11472            "Context menu should be visible for placeholder choices"
11473        );
11474
11475        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11476
11477        assert_state(editor, cx, "fn  = «valueˇ»;•");
11478
11479        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11480
11481        assert_state(editor, cx, "fn «» = value;•");
11482
11483        assert!(
11484            editor.context_menu_visible(),
11485            "Context menu should be visible again after returning to first tabstop"
11486        );
11487
11488        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11489
11490        assert_state(editor, cx, "fn «» = value;•");
11491    });
11492}
11493
11494#[gpui::test]
11495async fn test_snippets(cx: &mut TestAppContext) {
11496    init_test(cx, |_| {});
11497
11498    let mut cx = EditorTestContext::new(cx).await;
11499
11500    cx.set_state(indoc! {"
11501        a.ˇ b
11502        a.ˇ b
11503        a.ˇ b
11504    "});
11505
11506    cx.update_editor(|editor, window, cx| {
11507        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11508        let insertion_ranges = editor
11509            .selections
11510            .all(&editor.display_snapshot(cx))
11511            .iter()
11512            .map(|s| s.range())
11513            .collect::<Vec<_>>();
11514        editor
11515            .insert_snippet(&insertion_ranges, snippet, window, cx)
11516            .unwrap();
11517    });
11518
11519    cx.assert_editor_state(indoc! {"
11520        a.f(«oneˇ», two, «threeˇ») b
11521        a.f(«oneˇ», two, «threeˇ») b
11522        a.f(«oneˇ», two, «threeˇ») b
11523    "});
11524
11525    // Can't move earlier than the first tab stop
11526    cx.update_editor(|editor, window, cx| {
11527        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11528    });
11529    cx.assert_editor_state(indoc! {"
11530        a.f(«oneˇ», two, «threeˇ») b
11531        a.f(«oneˇ», two, «threeˇ») b
11532        a.f(«oneˇ», two, «threeˇ») b
11533    "});
11534
11535    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11536    cx.assert_editor_state(indoc! {"
11537        a.f(one, «twoˇ», three) b
11538        a.f(one, «twoˇ», three) b
11539        a.f(one, «twoˇ», three) b
11540    "});
11541
11542    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11543    cx.assert_editor_state(indoc! {"
11544        a.f(«oneˇ», two, «threeˇ») b
11545        a.f(«oneˇ», two, «threeˇ») b
11546        a.f(«oneˇ», two, «threeˇ») b
11547    "});
11548
11549    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11550    cx.assert_editor_state(indoc! {"
11551        a.f(one, «twoˇ», three) b
11552        a.f(one, «twoˇ», three) b
11553        a.f(one, «twoˇ», three) b
11554    "});
11555    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11556    cx.assert_editor_state(indoc! {"
11557        a.f(one, two, three)ˇ b
11558        a.f(one, two, three)ˇ b
11559        a.f(one, two, three)ˇ b
11560    "});
11561
11562    // As soon as the last tab stop is reached, snippet state is gone
11563    cx.update_editor(|editor, window, cx| {
11564        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11565    });
11566    cx.assert_editor_state(indoc! {"
11567        a.f(one, two, three)ˇ b
11568        a.f(one, two, three)ˇ b
11569        a.f(one, two, three)ˇ b
11570    "});
11571}
11572
11573#[gpui::test]
11574async fn test_snippet_indentation(cx: &mut TestAppContext) {
11575    init_test(cx, |_| {});
11576
11577    let mut cx = EditorTestContext::new(cx).await;
11578
11579    cx.update_editor(|editor, window, cx| {
11580        let snippet = Snippet::parse(indoc! {"
11581            /*
11582             * Multiline comment with leading indentation
11583             *
11584             * $1
11585             */
11586            $0"})
11587        .unwrap();
11588        let insertion_ranges = editor
11589            .selections
11590            .all(&editor.display_snapshot(cx))
11591            .iter()
11592            .map(|s| s.range())
11593            .collect::<Vec<_>>();
11594        editor
11595            .insert_snippet(&insertion_ranges, snippet, window, cx)
11596            .unwrap();
11597    });
11598
11599    cx.assert_editor_state(indoc! {"
11600        /*
11601         * Multiline comment with leading indentation
11602         *
11603         * ˇ
11604         */
11605    "});
11606
11607    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11608    cx.assert_editor_state(indoc! {"
11609        /*
11610         * Multiline comment with leading indentation
11611         *
11612         *•
11613         */
11614        ˇ"});
11615}
11616
11617#[gpui::test]
11618async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11619    init_test(cx, |_| {});
11620
11621    let mut cx = EditorTestContext::new(cx).await;
11622    cx.update_editor(|editor, _, cx| {
11623        editor.project().unwrap().update(cx, |project, cx| {
11624            project.snippets().update(cx, |snippets, _cx| {
11625                let snippet = project::snippet_provider::Snippet {
11626                    prefix: vec!["multi word".to_string()],
11627                    body: "this is many words".to_string(),
11628                    description: Some("description".to_string()),
11629                    name: "multi-word snippet test".to_string(),
11630                };
11631                snippets.add_snippet_for_test(
11632                    None,
11633                    PathBuf::from("test_snippets.json"),
11634                    vec![Arc::new(snippet)],
11635                );
11636            });
11637        })
11638    });
11639
11640    for (input_to_simulate, should_match_snippet) in [
11641        ("m", true),
11642        ("m ", true),
11643        ("m w", true),
11644        ("aa m w", true),
11645        ("aa m g", false),
11646    ] {
11647        cx.set_state("ˇ");
11648        cx.simulate_input(input_to_simulate); // fails correctly
11649
11650        cx.update_editor(|editor, _, _| {
11651            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11652            else {
11653                assert!(!should_match_snippet); // no completions! don't even show the menu
11654                return;
11655            };
11656            assert!(context_menu.visible());
11657            let completions = context_menu.completions.borrow();
11658
11659            assert_eq!(!completions.is_empty(), should_match_snippet);
11660        });
11661    }
11662}
11663
11664#[gpui::test]
11665async fn test_document_format_during_save(cx: &mut TestAppContext) {
11666    init_test(cx, |_| {});
11667
11668    let fs = FakeFs::new(cx.executor());
11669    fs.insert_file(path!("/file.rs"), Default::default()).await;
11670
11671    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11672
11673    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11674    language_registry.add(rust_lang());
11675    let mut fake_servers = language_registry.register_fake_lsp(
11676        "Rust",
11677        FakeLspAdapter {
11678            capabilities: lsp::ServerCapabilities {
11679                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11680                ..Default::default()
11681            },
11682            ..Default::default()
11683        },
11684    );
11685
11686    let buffer = project
11687        .update(cx, |project, cx| {
11688            project.open_local_buffer(path!("/file.rs"), cx)
11689        })
11690        .await
11691        .unwrap();
11692
11693    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11694    let (editor, cx) = cx.add_window_view(|window, cx| {
11695        build_editor_with_project(project.clone(), buffer, window, cx)
11696    });
11697    editor.update_in(cx, |editor, window, cx| {
11698        editor.set_text("one\ntwo\nthree\n", window, cx)
11699    });
11700    assert!(cx.read(|cx| editor.is_dirty(cx)));
11701
11702    cx.executor().start_waiting();
11703    let fake_server = fake_servers.next().await.unwrap();
11704
11705    {
11706        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11707            move |params, _| async move {
11708                assert_eq!(
11709                    params.text_document.uri,
11710                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11711                );
11712                assert_eq!(params.options.tab_size, 4);
11713                Ok(Some(vec![lsp::TextEdit::new(
11714                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11715                    ", ".to_string(),
11716                )]))
11717            },
11718        );
11719        let save = editor
11720            .update_in(cx, |editor, window, cx| {
11721                editor.save(
11722                    SaveOptions {
11723                        format: true,
11724                        autosave: false,
11725                    },
11726                    project.clone(),
11727                    window,
11728                    cx,
11729                )
11730            })
11731            .unwrap();
11732        cx.executor().start_waiting();
11733        save.await;
11734
11735        assert_eq!(
11736            editor.update(cx, |editor, cx| editor.text(cx)),
11737            "one, two\nthree\n"
11738        );
11739        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11740    }
11741
11742    {
11743        editor.update_in(cx, |editor, window, cx| {
11744            editor.set_text("one\ntwo\nthree\n", window, cx)
11745        });
11746        assert!(cx.read(|cx| editor.is_dirty(cx)));
11747
11748        // Ensure we can still save even if formatting hangs.
11749        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11750            move |params, _| async move {
11751                assert_eq!(
11752                    params.text_document.uri,
11753                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11754                );
11755                futures::future::pending::<()>().await;
11756                unreachable!()
11757            },
11758        );
11759        let save = editor
11760            .update_in(cx, |editor, window, cx| {
11761                editor.save(
11762                    SaveOptions {
11763                        format: true,
11764                        autosave: false,
11765                    },
11766                    project.clone(),
11767                    window,
11768                    cx,
11769                )
11770            })
11771            .unwrap();
11772        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11773        cx.executor().start_waiting();
11774        save.await;
11775        assert_eq!(
11776            editor.update(cx, |editor, cx| editor.text(cx)),
11777            "one\ntwo\nthree\n"
11778        );
11779    }
11780
11781    // Set rust language override and assert overridden tabsize is sent to language server
11782    update_test_language_settings(cx, |settings| {
11783        settings.languages.0.insert(
11784            "Rust".into(),
11785            LanguageSettingsContent {
11786                tab_size: NonZeroU32::new(8),
11787                ..Default::default()
11788            },
11789        );
11790    });
11791
11792    {
11793        editor.update_in(cx, |editor, window, cx| {
11794            editor.set_text("somehting_new\n", window, cx)
11795        });
11796        assert!(cx.read(|cx| editor.is_dirty(cx)));
11797        let _formatting_request_signal = fake_server
11798            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11799                assert_eq!(
11800                    params.text_document.uri,
11801                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11802                );
11803                assert_eq!(params.options.tab_size, 8);
11804                Ok(Some(vec![]))
11805            });
11806        let save = editor
11807            .update_in(cx, |editor, window, cx| {
11808                editor.save(
11809                    SaveOptions {
11810                        format: true,
11811                        autosave: false,
11812                    },
11813                    project.clone(),
11814                    window,
11815                    cx,
11816                )
11817            })
11818            .unwrap();
11819        cx.executor().start_waiting();
11820        save.await;
11821    }
11822}
11823
11824#[gpui::test]
11825async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11826    init_test(cx, |settings| {
11827        settings.defaults.ensure_final_newline_on_save = Some(false);
11828    });
11829
11830    let fs = FakeFs::new(cx.executor());
11831    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11832
11833    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11834
11835    let buffer = project
11836        .update(cx, |project, cx| {
11837            project.open_local_buffer(path!("/file.txt"), cx)
11838        })
11839        .await
11840        .unwrap();
11841
11842    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11843    let (editor, cx) = cx.add_window_view(|window, cx| {
11844        build_editor_with_project(project.clone(), buffer, window, cx)
11845    });
11846    editor.update_in(cx, |editor, window, cx| {
11847        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11848            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11849        });
11850    });
11851    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11852
11853    editor.update_in(cx, |editor, window, cx| {
11854        editor.handle_input("\n", window, cx)
11855    });
11856    cx.run_until_parked();
11857    save(&editor, &project, cx).await;
11858    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11859
11860    editor.update_in(cx, |editor, window, cx| {
11861        editor.undo(&Default::default(), window, cx);
11862    });
11863    save(&editor, &project, cx).await;
11864    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11865
11866    editor.update_in(cx, |editor, window, cx| {
11867        editor.redo(&Default::default(), window, cx);
11868    });
11869    cx.run_until_parked();
11870    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11871
11872    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11873        let save = editor
11874            .update_in(cx, |editor, window, cx| {
11875                editor.save(
11876                    SaveOptions {
11877                        format: true,
11878                        autosave: false,
11879                    },
11880                    project.clone(),
11881                    window,
11882                    cx,
11883                )
11884            })
11885            .unwrap();
11886        cx.executor().start_waiting();
11887        save.await;
11888        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11889    }
11890}
11891
11892#[gpui::test]
11893async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11894    init_test(cx, |_| {});
11895
11896    let cols = 4;
11897    let rows = 10;
11898    let sample_text_1 = sample_text(rows, cols, 'a');
11899    assert_eq!(
11900        sample_text_1,
11901        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11902    );
11903    let sample_text_2 = sample_text(rows, cols, 'l');
11904    assert_eq!(
11905        sample_text_2,
11906        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11907    );
11908    let sample_text_3 = sample_text(rows, cols, 'v');
11909    assert_eq!(
11910        sample_text_3,
11911        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11912    );
11913
11914    let fs = FakeFs::new(cx.executor());
11915    fs.insert_tree(
11916        path!("/a"),
11917        json!({
11918            "main.rs": sample_text_1,
11919            "other.rs": sample_text_2,
11920            "lib.rs": sample_text_3,
11921        }),
11922    )
11923    .await;
11924
11925    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11926    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11927    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11928
11929    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11930    language_registry.add(rust_lang());
11931    let mut fake_servers = language_registry.register_fake_lsp(
11932        "Rust",
11933        FakeLspAdapter {
11934            capabilities: lsp::ServerCapabilities {
11935                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11936                ..Default::default()
11937            },
11938            ..Default::default()
11939        },
11940    );
11941
11942    let worktree = project.update(cx, |project, cx| {
11943        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11944        assert_eq!(worktrees.len(), 1);
11945        worktrees.pop().unwrap()
11946    });
11947    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11948
11949    let buffer_1 = project
11950        .update(cx, |project, cx| {
11951            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11952        })
11953        .await
11954        .unwrap();
11955    let buffer_2 = project
11956        .update(cx, |project, cx| {
11957            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11958        })
11959        .await
11960        .unwrap();
11961    let buffer_3 = project
11962        .update(cx, |project, cx| {
11963            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11964        })
11965        .await
11966        .unwrap();
11967
11968    let multi_buffer = cx.new(|cx| {
11969        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11970        multi_buffer.push_excerpts(
11971            buffer_1.clone(),
11972            [
11973                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11974                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11975                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11976            ],
11977            cx,
11978        );
11979        multi_buffer.push_excerpts(
11980            buffer_2.clone(),
11981            [
11982                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11983                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11984                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11985            ],
11986            cx,
11987        );
11988        multi_buffer.push_excerpts(
11989            buffer_3.clone(),
11990            [
11991                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11992                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11993                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11994            ],
11995            cx,
11996        );
11997        multi_buffer
11998    });
11999    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
12000        Editor::new(
12001            EditorMode::full(),
12002            multi_buffer,
12003            Some(project.clone()),
12004            window,
12005            cx,
12006        )
12007    });
12008
12009    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12010        editor.change_selections(
12011            SelectionEffects::scroll(Autoscroll::Next),
12012            window,
12013            cx,
12014            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
12015        );
12016        editor.insert("|one|two|three|", window, cx);
12017    });
12018    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12019    multi_buffer_editor.update_in(cx, |editor, window, cx| {
12020        editor.change_selections(
12021            SelectionEffects::scroll(Autoscroll::Next),
12022            window,
12023            cx,
12024            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
12025        );
12026        editor.insert("|four|five|six|", window, cx);
12027    });
12028    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
12029
12030    // First two buffers should be edited, but not the third one.
12031    assert_eq!(
12032        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12033        "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}",
12034    );
12035    buffer_1.update(cx, |buffer, _| {
12036        assert!(buffer.is_dirty());
12037        assert_eq!(
12038            buffer.text(),
12039            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
12040        )
12041    });
12042    buffer_2.update(cx, |buffer, _| {
12043        assert!(buffer.is_dirty());
12044        assert_eq!(
12045            buffer.text(),
12046            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
12047        )
12048    });
12049    buffer_3.update(cx, |buffer, _| {
12050        assert!(!buffer.is_dirty());
12051        assert_eq!(buffer.text(), sample_text_3,)
12052    });
12053    cx.executor().run_until_parked();
12054
12055    cx.executor().start_waiting();
12056    let save = multi_buffer_editor
12057        .update_in(cx, |editor, window, cx| {
12058            editor.save(
12059                SaveOptions {
12060                    format: true,
12061                    autosave: false,
12062                },
12063                project.clone(),
12064                window,
12065                cx,
12066            )
12067        })
12068        .unwrap();
12069
12070    let fake_server = fake_servers.next().await.unwrap();
12071    fake_server
12072        .server
12073        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
12074            Ok(Some(vec![lsp::TextEdit::new(
12075                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12076                format!("[{} formatted]", params.text_document.uri),
12077            )]))
12078        })
12079        .detach();
12080    save.await;
12081
12082    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
12083    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
12084    assert_eq!(
12085        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
12086        uri!(
12087            "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}"
12088        ),
12089    );
12090    buffer_1.update(cx, |buffer, _| {
12091        assert!(!buffer.is_dirty());
12092        assert_eq!(
12093            buffer.text(),
12094            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
12095        )
12096    });
12097    buffer_2.update(cx, |buffer, _| {
12098        assert!(!buffer.is_dirty());
12099        assert_eq!(
12100            buffer.text(),
12101            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
12102        )
12103    });
12104    buffer_3.update(cx, |buffer, _| {
12105        assert!(!buffer.is_dirty());
12106        assert_eq!(buffer.text(), sample_text_3,)
12107    });
12108}
12109
12110#[gpui::test]
12111async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12112    init_test(cx, |_| {});
12113
12114    let fs = FakeFs::new(cx.executor());
12115    fs.insert_tree(
12116        path!("/dir"),
12117        json!({
12118            "file1.rs": "fn main() { println!(\"hello\"); }",
12119            "file2.rs": "fn test() { println!(\"test\"); }",
12120            "file3.rs": "fn other() { println!(\"other\"); }\n",
12121        }),
12122    )
12123    .await;
12124
12125    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12126    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12127    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12128
12129    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12130    language_registry.add(rust_lang());
12131
12132    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12133    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12134
12135    // Open three buffers
12136    let buffer_1 = project
12137        .update(cx, |project, cx| {
12138            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12139        })
12140        .await
12141        .unwrap();
12142    let buffer_2 = project
12143        .update(cx, |project, cx| {
12144            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12145        })
12146        .await
12147        .unwrap();
12148    let buffer_3 = project
12149        .update(cx, |project, cx| {
12150            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12151        })
12152        .await
12153        .unwrap();
12154
12155    // Create a multi-buffer with all three buffers
12156    let multi_buffer = cx.new(|cx| {
12157        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12158        multi_buffer.push_excerpts(
12159            buffer_1.clone(),
12160            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12161            cx,
12162        );
12163        multi_buffer.push_excerpts(
12164            buffer_2.clone(),
12165            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12166            cx,
12167        );
12168        multi_buffer.push_excerpts(
12169            buffer_3.clone(),
12170            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12171            cx,
12172        );
12173        multi_buffer
12174    });
12175
12176    let editor = cx.new_window_entity(|window, cx| {
12177        Editor::new(
12178            EditorMode::full(),
12179            multi_buffer,
12180            Some(project.clone()),
12181            window,
12182            cx,
12183        )
12184    });
12185
12186    // Edit only the first buffer
12187    editor.update_in(cx, |editor, window, cx| {
12188        editor.change_selections(
12189            SelectionEffects::scroll(Autoscroll::Next),
12190            window,
12191            cx,
12192            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12193        );
12194        editor.insert("// edited", window, cx);
12195    });
12196
12197    // Verify that only buffer 1 is dirty
12198    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12199    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12200    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12201
12202    // Get write counts after file creation (files were created with initial content)
12203    // We expect each file to have been written once during creation
12204    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12205    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12206    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12207
12208    // Perform autosave
12209    let save_task = editor.update_in(cx, |editor, window, cx| {
12210        editor.save(
12211            SaveOptions {
12212                format: true,
12213                autosave: true,
12214            },
12215            project.clone(),
12216            window,
12217            cx,
12218        )
12219    });
12220    save_task.await.unwrap();
12221
12222    // Only the dirty buffer should have been saved
12223    assert_eq!(
12224        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12225        1,
12226        "Buffer 1 was dirty, so it should have been written once during autosave"
12227    );
12228    assert_eq!(
12229        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12230        0,
12231        "Buffer 2 was clean, so it should not have been written during autosave"
12232    );
12233    assert_eq!(
12234        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12235        0,
12236        "Buffer 3 was clean, so it should not have been written during autosave"
12237    );
12238
12239    // Verify buffer states after autosave
12240    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12241    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12242    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12243
12244    // Now perform a manual save (format = true)
12245    let save_task = editor.update_in(cx, |editor, window, cx| {
12246        editor.save(
12247            SaveOptions {
12248                format: true,
12249                autosave: false,
12250            },
12251            project.clone(),
12252            window,
12253            cx,
12254        )
12255    });
12256    save_task.await.unwrap();
12257
12258    // During manual save, clean buffers don't get written to disk
12259    // They just get did_save called for language server notifications
12260    assert_eq!(
12261        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12262        1,
12263        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12264    );
12265    assert_eq!(
12266        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12267        0,
12268        "Buffer 2 should not have been written at all"
12269    );
12270    assert_eq!(
12271        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12272        0,
12273        "Buffer 3 should not have been written at all"
12274    );
12275}
12276
12277async fn setup_range_format_test(
12278    cx: &mut TestAppContext,
12279) -> (
12280    Entity<Project>,
12281    Entity<Editor>,
12282    &mut gpui::VisualTestContext,
12283    lsp::FakeLanguageServer,
12284) {
12285    init_test(cx, |_| {});
12286
12287    let fs = FakeFs::new(cx.executor());
12288    fs.insert_file(path!("/file.rs"), Default::default()).await;
12289
12290    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12291
12292    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12293    language_registry.add(rust_lang());
12294    let mut fake_servers = language_registry.register_fake_lsp(
12295        "Rust",
12296        FakeLspAdapter {
12297            capabilities: lsp::ServerCapabilities {
12298                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12299                ..lsp::ServerCapabilities::default()
12300            },
12301            ..FakeLspAdapter::default()
12302        },
12303    );
12304
12305    let buffer = project
12306        .update(cx, |project, cx| {
12307            project.open_local_buffer(path!("/file.rs"), cx)
12308        })
12309        .await
12310        .unwrap();
12311
12312    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12313    let (editor, cx) = cx.add_window_view(|window, cx| {
12314        build_editor_with_project(project.clone(), buffer, window, cx)
12315    });
12316
12317    cx.executor().start_waiting();
12318    let fake_server = fake_servers.next().await.unwrap();
12319
12320    (project, editor, cx, fake_server)
12321}
12322
12323#[gpui::test]
12324async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12325    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12326
12327    editor.update_in(cx, |editor, window, cx| {
12328        editor.set_text("one\ntwo\nthree\n", window, cx)
12329    });
12330    assert!(cx.read(|cx| editor.is_dirty(cx)));
12331
12332    let save = editor
12333        .update_in(cx, |editor, window, cx| {
12334            editor.save(
12335                SaveOptions {
12336                    format: true,
12337                    autosave: false,
12338                },
12339                project.clone(),
12340                window,
12341                cx,
12342            )
12343        })
12344        .unwrap();
12345    fake_server
12346        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12347            assert_eq!(
12348                params.text_document.uri,
12349                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12350            );
12351            assert_eq!(params.options.tab_size, 4);
12352            Ok(Some(vec![lsp::TextEdit::new(
12353                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12354                ", ".to_string(),
12355            )]))
12356        })
12357        .next()
12358        .await;
12359    cx.executor().start_waiting();
12360    save.await;
12361    assert_eq!(
12362        editor.update(cx, |editor, cx| editor.text(cx)),
12363        "one, two\nthree\n"
12364    );
12365    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12366}
12367
12368#[gpui::test]
12369async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12370    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12371
12372    editor.update_in(cx, |editor, window, cx| {
12373        editor.set_text("one\ntwo\nthree\n", window, cx)
12374    });
12375    assert!(cx.read(|cx| editor.is_dirty(cx)));
12376
12377    // Test that save still works when formatting hangs
12378    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12379        move |params, _| async move {
12380            assert_eq!(
12381                params.text_document.uri,
12382                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12383            );
12384            futures::future::pending::<()>().await;
12385            unreachable!()
12386        },
12387    );
12388    let save = editor
12389        .update_in(cx, |editor, window, cx| {
12390            editor.save(
12391                SaveOptions {
12392                    format: true,
12393                    autosave: false,
12394                },
12395                project.clone(),
12396                window,
12397                cx,
12398            )
12399        })
12400        .unwrap();
12401    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12402    cx.executor().start_waiting();
12403    save.await;
12404    assert_eq!(
12405        editor.update(cx, |editor, cx| editor.text(cx)),
12406        "one\ntwo\nthree\n"
12407    );
12408    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12409}
12410
12411#[gpui::test]
12412async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12413    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12414
12415    // Buffer starts clean, no formatting should be requested
12416    let save = editor
12417        .update_in(cx, |editor, window, cx| {
12418            editor.save(
12419                SaveOptions {
12420                    format: false,
12421                    autosave: false,
12422                },
12423                project.clone(),
12424                window,
12425                cx,
12426            )
12427        })
12428        .unwrap();
12429    let _pending_format_request = fake_server
12430        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12431            panic!("Should not be invoked");
12432        })
12433        .next();
12434    cx.executor().start_waiting();
12435    save.await;
12436    cx.run_until_parked();
12437}
12438
12439#[gpui::test]
12440async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12441    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12442
12443    // Set Rust language override and assert overridden tabsize is sent to language server
12444    update_test_language_settings(cx, |settings| {
12445        settings.languages.0.insert(
12446            "Rust".into(),
12447            LanguageSettingsContent {
12448                tab_size: NonZeroU32::new(8),
12449                ..Default::default()
12450            },
12451        );
12452    });
12453
12454    editor.update_in(cx, |editor, window, cx| {
12455        editor.set_text("something_new\n", window, cx)
12456    });
12457    assert!(cx.read(|cx| editor.is_dirty(cx)));
12458    let save = editor
12459        .update_in(cx, |editor, window, cx| {
12460            editor.save(
12461                SaveOptions {
12462                    format: true,
12463                    autosave: false,
12464                },
12465                project.clone(),
12466                window,
12467                cx,
12468            )
12469        })
12470        .unwrap();
12471    fake_server
12472        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12473            assert_eq!(
12474                params.text_document.uri,
12475                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12476            );
12477            assert_eq!(params.options.tab_size, 8);
12478            Ok(Some(Vec::new()))
12479        })
12480        .next()
12481        .await;
12482    save.await;
12483}
12484
12485#[gpui::test]
12486async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12487    init_test(cx, |settings| {
12488        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12489            settings::LanguageServerFormatterSpecifier::Current,
12490        )))
12491    });
12492
12493    let fs = FakeFs::new(cx.executor());
12494    fs.insert_file(path!("/file.rs"), Default::default()).await;
12495
12496    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12497
12498    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12499    language_registry.add(Arc::new(Language::new(
12500        LanguageConfig {
12501            name: "Rust".into(),
12502            matcher: LanguageMatcher {
12503                path_suffixes: vec!["rs".to_string()],
12504                ..Default::default()
12505            },
12506            ..LanguageConfig::default()
12507        },
12508        Some(tree_sitter_rust::LANGUAGE.into()),
12509    )));
12510    update_test_language_settings(cx, |settings| {
12511        // Enable Prettier formatting for the same buffer, and ensure
12512        // LSP is called instead of Prettier.
12513        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12514    });
12515    let mut fake_servers = language_registry.register_fake_lsp(
12516        "Rust",
12517        FakeLspAdapter {
12518            capabilities: lsp::ServerCapabilities {
12519                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12520                ..Default::default()
12521            },
12522            ..Default::default()
12523        },
12524    );
12525
12526    let buffer = project
12527        .update(cx, |project, cx| {
12528            project.open_local_buffer(path!("/file.rs"), cx)
12529        })
12530        .await
12531        .unwrap();
12532
12533    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12534    let (editor, cx) = cx.add_window_view(|window, cx| {
12535        build_editor_with_project(project.clone(), buffer, window, cx)
12536    });
12537    editor.update_in(cx, |editor, window, cx| {
12538        editor.set_text("one\ntwo\nthree\n", window, cx)
12539    });
12540
12541    cx.executor().start_waiting();
12542    let fake_server = fake_servers.next().await.unwrap();
12543
12544    let format = editor
12545        .update_in(cx, |editor, window, cx| {
12546            editor.perform_format(
12547                project.clone(),
12548                FormatTrigger::Manual,
12549                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12550                window,
12551                cx,
12552            )
12553        })
12554        .unwrap();
12555    fake_server
12556        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12557            assert_eq!(
12558                params.text_document.uri,
12559                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12560            );
12561            assert_eq!(params.options.tab_size, 4);
12562            Ok(Some(vec![lsp::TextEdit::new(
12563                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12564                ", ".to_string(),
12565            )]))
12566        })
12567        .next()
12568        .await;
12569    cx.executor().start_waiting();
12570    format.await;
12571    assert_eq!(
12572        editor.update(cx, |editor, cx| editor.text(cx)),
12573        "one, two\nthree\n"
12574    );
12575
12576    editor.update_in(cx, |editor, window, cx| {
12577        editor.set_text("one\ntwo\nthree\n", window, cx)
12578    });
12579    // Ensure we don't lock if formatting hangs.
12580    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12581        move |params, _| async move {
12582            assert_eq!(
12583                params.text_document.uri,
12584                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12585            );
12586            futures::future::pending::<()>().await;
12587            unreachable!()
12588        },
12589    );
12590    let format = editor
12591        .update_in(cx, |editor, window, cx| {
12592            editor.perform_format(
12593                project,
12594                FormatTrigger::Manual,
12595                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12596                window,
12597                cx,
12598            )
12599        })
12600        .unwrap();
12601    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12602    cx.executor().start_waiting();
12603    format.await;
12604    assert_eq!(
12605        editor.update(cx, |editor, cx| editor.text(cx)),
12606        "one\ntwo\nthree\n"
12607    );
12608}
12609
12610#[gpui::test]
12611async fn test_multiple_formatters(cx: &mut TestAppContext) {
12612    init_test(cx, |settings| {
12613        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12614        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12615            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12616            Formatter::CodeAction("code-action-1".into()),
12617            Formatter::CodeAction("code-action-2".into()),
12618        ]))
12619    });
12620
12621    let fs = FakeFs::new(cx.executor());
12622    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12623        .await;
12624
12625    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12626    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12627    language_registry.add(rust_lang());
12628
12629    let mut fake_servers = language_registry.register_fake_lsp(
12630        "Rust",
12631        FakeLspAdapter {
12632            capabilities: lsp::ServerCapabilities {
12633                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12634                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12635                    commands: vec!["the-command-for-code-action-1".into()],
12636                    ..Default::default()
12637                }),
12638                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12639                ..Default::default()
12640            },
12641            ..Default::default()
12642        },
12643    );
12644
12645    let buffer = project
12646        .update(cx, |project, cx| {
12647            project.open_local_buffer(path!("/file.rs"), cx)
12648        })
12649        .await
12650        .unwrap();
12651
12652    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12653    let (editor, cx) = cx.add_window_view(|window, cx| {
12654        build_editor_with_project(project.clone(), buffer, window, cx)
12655    });
12656
12657    cx.executor().start_waiting();
12658
12659    let fake_server = fake_servers.next().await.unwrap();
12660    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12661        move |_params, _| async move {
12662            Ok(Some(vec![lsp::TextEdit::new(
12663                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12664                "applied-formatting\n".to_string(),
12665            )]))
12666        },
12667    );
12668    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12669        move |params, _| async move {
12670            let requested_code_actions = params.context.only.expect("Expected code action request");
12671            assert_eq!(requested_code_actions.len(), 1);
12672
12673            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12674            let code_action = match requested_code_actions[0].as_str() {
12675                "code-action-1" => lsp::CodeAction {
12676                    kind: Some("code-action-1".into()),
12677                    edit: Some(lsp::WorkspaceEdit::new(
12678                        [(
12679                            uri,
12680                            vec![lsp::TextEdit::new(
12681                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12682                                "applied-code-action-1-edit\n".to_string(),
12683                            )],
12684                        )]
12685                        .into_iter()
12686                        .collect(),
12687                    )),
12688                    command: Some(lsp::Command {
12689                        command: "the-command-for-code-action-1".into(),
12690                        ..Default::default()
12691                    }),
12692                    ..Default::default()
12693                },
12694                "code-action-2" => lsp::CodeAction {
12695                    kind: Some("code-action-2".into()),
12696                    edit: Some(lsp::WorkspaceEdit::new(
12697                        [(
12698                            uri,
12699                            vec![lsp::TextEdit::new(
12700                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12701                                "applied-code-action-2-edit\n".to_string(),
12702                            )],
12703                        )]
12704                        .into_iter()
12705                        .collect(),
12706                    )),
12707                    ..Default::default()
12708                },
12709                req => panic!("Unexpected code action request: {:?}", req),
12710            };
12711            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12712                code_action,
12713            )]))
12714        },
12715    );
12716
12717    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12718        move |params, _| async move { Ok(params) }
12719    });
12720
12721    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12722    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12723        let fake = fake_server.clone();
12724        let lock = command_lock.clone();
12725        move |params, _| {
12726            assert_eq!(params.command, "the-command-for-code-action-1");
12727            let fake = fake.clone();
12728            let lock = lock.clone();
12729            async move {
12730                lock.lock().await;
12731                fake.server
12732                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12733                        label: None,
12734                        edit: lsp::WorkspaceEdit {
12735                            changes: Some(
12736                                [(
12737                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12738                                    vec![lsp::TextEdit {
12739                                        range: lsp::Range::new(
12740                                            lsp::Position::new(0, 0),
12741                                            lsp::Position::new(0, 0),
12742                                        ),
12743                                        new_text: "applied-code-action-1-command\n".into(),
12744                                    }],
12745                                )]
12746                                .into_iter()
12747                                .collect(),
12748                            ),
12749                            ..Default::default()
12750                        },
12751                    })
12752                    .await
12753                    .into_response()
12754                    .unwrap();
12755                Ok(Some(json!(null)))
12756            }
12757        }
12758    });
12759
12760    cx.executor().start_waiting();
12761    editor
12762        .update_in(cx, |editor, window, cx| {
12763            editor.perform_format(
12764                project.clone(),
12765                FormatTrigger::Manual,
12766                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12767                window,
12768                cx,
12769            )
12770        })
12771        .unwrap()
12772        .await;
12773    editor.update(cx, |editor, cx| {
12774        assert_eq!(
12775            editor.text(cx),
12776            r#"
12777                applied-code-action-2-edit
12778                applied-code-action-1-command
12779                applied-code-action-1-edit
12780                applied-formatting
12781                one
12782                two
12783                three
12784            "#
12785            .unindent()
12786        );
12787    });
12788
12789    editor.update_in(cx, |editor, window, cx| {
12790        editor.undo(&Default::default(), window, cx);
12791        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12792    });
12793
12794    // Perform a manual edit while waiting for an LSP command
12795    // that's being run as part of a formatting code action.
12796    let lock_guard = command_lock.lock().await;
12797    let format = editor
12798        .update_in(cx, |editor, window, cx| {
12799            editor.perform_format(
12800                project.clone(),
12801                FormatTrigger::Manual,
12802                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12803                window,
12804                cx,
12805            )
12806        })
12807        .unwrap();
12808    cx.run_until_parked();
12809    editor.update(cx, |editor, cx| {
12810        assert_eq!(
12811            editor.text(cx),
12812            r#"
12813                applied-code-action-1-edit
12814                applied-formatting
12815                one
12816                two
12817                three
12818            "#
12819            .unindent()
12820        );
12821
12822        editor.buffer.update(cx, |buffer, cx| {
12823            let ix = buffer.len(cx);
12824            buffer.edit([(ix..ix, "edited\n")], None, cx);
12825        });
12826    });
12827
12828    // Allow the LSP command to proceed. Because the buffer was edited,
12829    // the second code action will not be run.
12830    drop(lock_guard);
12831    format.await;
12832    editor.update_in(cx, |editor, window, cx| {
12833        assert_eq!(
12834            editor.text(cx),
12835            r#"
12836                applied-code-action-1-command
12837                applied-code-action-1-edit
12838                applied-formatting
12839                one
12840                two
12841                three
12842                edited
12843            "#
12844            .unindent()
12845        );
12846
12847        // The manual edit is undone first, because it is the last thing the user did
12848        // (even though the command completed afterwards).
12849        editor.undo(&Default::default(), window, cx);
12850        assert_eq!(
12851            editor.text(cx),
12852            r#"
12853                applied-code-action-1-command
12854                applied-code-action-1-edit
12855                applied-formatting
12856                one
12857                two
12858                three
12859            "#
12860            .unindent()
12861        );
12862
12863        // All the formatting (including the command, which completed after the manual edit)
12864        // is undone together.
12865        editor.undo(&Default::default(), window, cx);
12866        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12867    });
12868}
12869
12870#[gpui::test]
12871async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12872    init_test(cx, |settings| {
12873        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12874            settings::LanguageServerFormatterSpecifier::Current,
12875        )]))
12876    });
12877
12878    let fs = FakeFs::new(cx.executor());
12879    fs.insert_file(path!("/file.ts"), Default::default()).await;
12880
12881    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12882
12883    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12884    language_registry.add(Arc::new(Language::new(
12885        LanguageConfig {
12886            name: "TypeScript".into(),
12887            matcher: LanguageMatcher {
12888                path_suffixes: vec!["ts".to_string()],
12889                ..Default::default()
12890            },
12891            ..LanguageConfig::default()
12892        },
12893        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12894    )));
12895    update_test_language_settings(cx, |settings| {
12896        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12897    });
12898    let mut fake_servers = language_registry.register_fake_lsp(
12899        "TypeScript",
12900        FakeLspAdapter {
12901            capabilities: lsp::ServerCapabilities {
12902                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12903                ..Default::default()
12904            },
12905            ..Default::default()
12906        },
12907    );
12908
12909    let buffer = project
12910        .update(cx, |project, cx| {
12911            project.open_local_buffer(path!("/file.ts"), cx)
12912        })
12913        .await
12914        .unwrap();
12915
12916    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12917    let (editor, cx) = cx.add_window_view(|window, cx| {
12918        build_editor_with_project(project.clone(), buffer, window, cx)
12919    });
12920    editor.update_in(cx, |editor, window, cx| {
12921        editor.set_text(
12922            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12923            window,
12924            cx,
12925        )
12926    });
12927
12928    cx.executor().start_waiting();
12929    let fake_server = fake_servers.next().await.unwrap();
12930
12931    let format = editor
12932        .update_in(cx, |editor, window, cx| {
12933            editor.perform_code_action_kind(
12934                project.clone(),
12935                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12936                window,
12937                cx,
12938            )
12939        })
12940        .unwrap();
12941    fake_server
12942        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12943            assert_eq!(
12944                params.text_document.uri,
12945                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12946            );
12947            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12948                lsp::CodeAction {
12949                    title: "Organize Imports".to_string(),
12950                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12951                    edit: Some(lsp::WorkspaceEdit {
12952                        changes: Some(
12953                            [(
12954                                params.text_document.uri.clone(),
12955                                vec![lsp::TextEdit::new(
12956                                    lsp::Range::new(
12957                                        lsp::Position::new(1, 0),
12958                                        lsp::Position::new(2, 0),
12959                                    ),
12960                                    "".to_string(),
12961                                )],
12962                            )]
12963                            .into_iter()
12964                            .collect(),
12965                        ),
12966                        ..Default::default()
12967                    }),
12968                    ..Default::default()
12969                },
12970            )]))
12971        })
12972        .next()
12973        .await;
12974    cx.executor().start_waiting();
12975    format.await;
12976    assert_eq!(
12977        editor.update(cx, |editor, cx| editor.text(cx)),
12978        "import { a } from 'module';\n\nconst x = a;\n"
12979    );
12980
12981    editor.update_in(cx, |editor, window, cx| {
12982        editor.set_text(
12983            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12984            window,
12985            cx,
12986        )
12987    });
12988    // Ensure we don't lock if code action hangs.
12989    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12990        move |params, _| async move {
12991            assert_eq!(
12992                params.text_document.uri,
12993                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12994            );
12995            futures::future::pending::<()>().await;
12996            unreachable!()
12997        },
12998    );
12999    let format = editor
13000        .update_in(cx, |editor, window, cx| {
13001            editor.perform_code_action_kind(
13002                project,
13003                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
13004                window,
13005                cx,
13006            )
13007        })
13008        .unwrap();
13009    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
13010    cx.executor().start_waiting();
13011    format.await;
13012    assert_eq!(
13013        editor.update(cx, |editor, cx| editor.text(cx)),
13014        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
13015    );
13016}
13017
13018#[gpui::test]
13019async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
13020    init_test(cx, |_| {});
13021
13022    let mut cx = EditorLspTestContext::new_rust(
13023        lsp::ServerCapabilities {
13024            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13025            ..Default::default()
13026        },
13027        cx,
13028    )
13029    .await;
13030
13031    cx.set_state(indoc! {"
13032        one.twoˇ
13033    "});
13034
13035    // The format request takes a long time. When it completes, it inserts
13036    // a newline and an indent before the `.`
13037    cx.lsp
13038        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
13039            let executor = cx.background_executor().clone();
13040            async move {
13041                executor.timer(Duration::from_millis(100)).await;
13042                Ok(Some(vec![lsp::TextEdit {
13043                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
13044                    new_text: "\n    ".into(),
13045                }]))
13046            }
13047        });
13048
13049    // Submit a format request.
13050    let format_1 = cx
13051        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13052        .unwrap();
13053    cx.executor().run_until_parked();
13054
13055    // Submit a second format request.
13056    let format_2 = cx
13057        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13058        .unwrap();
13059    cx.executor().run_until_parked();
13060
13061    // Wait for both format requests to complete
13062    cx.executor().advance_clock(Duration::from_millis(200));
13063    cx.executor().start_waiting();
13064    format_1.await.unwrap();
13065    cx.executor().start_waiting();
13066    format_2.await.unwrap();
13067
13068    // The formatting edits only happens once.
13069    cx.assert_editor_state(indoc! {"
13070        one
13071            .twoˇ
13072    "});
13073}
13074
13075#[gpui::test]
13076async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
13077    init_test(cx, |settings| {
13078        settings.defaults.formatter = Some(FormatterList::default())
13079    });
13080
13081    let mut cx = EditorLspTestContext::new_rust(
13082        lsp::ServerCapabilities {
13083            document_formatting_provider: Some(lsp::OneOf::Left(true)),
13084            ..Default::default()
13085        },
13086        cx,
13087    )
13088    .await;
13089
13090    // Record which buffer changes have been sent to the language server
13091    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
13092    cx.lsp
13093        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
13094            let buffer_changes = buffer_changes.clone();
13095            move |params, _| {
13096                buffer_changes.lock().extend(
13097                    params
13098                        .content_changes
13099                        .into_iter()
13100                        .map(|e| (e.range.unwrap(), e.text)),
13101                );
13102            }
13103        });
13104    // Handle formatting requests to the language server.
13105    cx.lsp
13106        .set_request_handler::<lsp::request::Formatting, _, _>({
13107            let buffer_changes = buffer_changes.clone();
13108            move |_, _| {
13109                let buffer_changes = buffer_changes.clone();
13110                // Insert blank lines between each line of the buffer.
13111                async move {
13112                    // When formatting is requested, trailing whitespace has already been stripped,
13113                    // and the trailing newline has already been added.
13114                    assert_eq!(
13115                        &buffer_changes.lock()[1..],
13116                        &[
13117                            (
13118                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13119                                "".into()
13120                            ),
13121                            (
13122                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13123                                "".into()
13124                            ),
13125                            (
13126                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13127                                "\n".into()
13128                            ),
13129                        ]
13130                    );
13131
13132                    Ok(Some(vec![
13133                        lsp::TextEdit {
13134                            range: lsp::Range::new(
13135                                lsp::Position::new(1, 0),
13136                                lsp::Position::new(1, 0),
13137                            ),
13138                            new_text: "\n".into(),
13139                        },
13140                        lsp::TextEdit {
13141                            range: lsp::Range::new(
13142                                lsp::Position::new(2, 0),
13143                                lsp::Position::new(2, 0),
13144                            ),
13145                            new_text: "\n".into(),
13146                        },
13147                    ]))
13148                }
13149            }
13150        });
13151
13152    // Set up a buffer white some trailing whitespace and no trailing newline.
13153    cx.set_state(
13154        &[
13155            "one ",   //
13156            "twoˇ",   //
13157            "three ", //
13158            "four",   //
13159        ]
13160        .join("\n"),
13161    );
13162    cx.run_until_parked();
13163
13164    // Submit a format request.
13165    let format = cx
13166        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13167        .unwrap();
13168
13169    cx.run_until_parked();
13170    // After formatting the buffer, the trailing whitespace is stripped,
13171    // a newline is appended, and the edits provided by the language server
13172    // have been applied.
13173    format.await.unwrap();
13174
13175    cx.assert_editor_state(
13176        &[
13177            "one",   //
13178            "",      //
13179            "twoˇ",  //
13180            "",      //
13181            "three", //
13182            "four",  //
13183            "",      //
13184        ]
13185        .join("\n"),
13186    );
13187
13188    // Undoing the formatting undoes the trailing whitespace removal, the
13189    // trailing newline, and the LSP edits.
13190    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13191    cx.assert_editor_state(
13192        &[
13193            "one ",   //
13194            "twoˇ",   //
13195            "three ", //
13196            "four",   //
13197        ]
13198        .join("\n"),
13199    );
13200}
13201
13202#[gpui::test]
13203async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13204    cx: &mut TestAppContext,
13205) {
13206    init_test(cx, |_| {});
13207
13208    cx.update(|cx| {
13209        cx.update_global::<SettingsStore, _>(|settings, cx| {
13210            settings.update_user_settings(cx, |settings| {
13211                settings.editor.auto_signature_help = Some(true);
13212            });
13213        });
13214    });
13215
13216    let mut cx = EditorLspTestContext::new_rust(
13217        lsp::ServerCapabilities {
13218            signature_help_provider: Some(lsp::SignatureHelpOptions {
13219                ..Default::default()
13220            }),
13221            ..Default::default()
13222        },
13223        cx,
13224    )
13225    .await;
13226
13227    let language = Language::new(
13228        LanguageConfig {
13229            name: "Rust".into(),
13230            brackets: BracketPairConfig {
13231                pairs: vec![
13232                    BracketPair {
13233                        start: "{".to_string(),
13234                        end: "}".to_string(),
13235                        close: true,
13236                        surround: true,
13237                        newline: true,
13238                    },
13239                    BracketPair {
13240                        start: "(".to_string(),
13241                        end: ")".to_string(),
13242                        close: true,
13243                        surround: true,
13244                        newline: true,
13245                    },
13246                    BracketPair {
13247                        start: "/*".to_string(),
13248                        end: " */".to_string(),
13249                        close: true,
13250                        surround: true,
13251                        newline: true,
13252                    },
13253                    BracketPair {
13254                        start: "[".to_string(),
13255                        end: "]".to_string(),
13256                        close: false,
13257                        surround: false,
13258                        newline: true,
13259                    },
13260                    BracketPair {
13261                        start: "\"".to_string(),
13262                        end: "\"".to_string(),
13263                        close: true,
13264                        surround: true,
13265                        newline: false,
13266                    },
13267                    BracketPair {
13268                        start: "<".to_string(),
13269                        end: ">".to_string(),
13270                        close: false,
13271                        surround: true,
13272                        newline: true,
13273                    },
13274                ],
13275                ..Default::default()
13276            },
13277            autoclose_before: "})]".to_string(),
13278            ..Default::default()
13279        },
13280        Some(tree_sitter_rust::LANGUAGE.into()),
13281    );
13282    let language = Arc::new(language);
13283
13284    cx.language_registry().add(language.clone());
13285    cx.update_buffer(|buffer, cx| {
13286        buffer.set_language(Some(language), cx);
13287    });
13288
13289    cx.set_state(
13290        &r#"
13291            fn main() {
13292                sampleˇ
13293            }
13294        "#
13295        .unindent(),
13296    );
13297
13298    cx.update_editor(|editor, window, cx| {
13299        editor.handle_input("(", window, cx);
13300    });
13301    cx.assert_editor_state(
13302        &"
13303            fn main() {
13304                sample(ˇ)
13305            }
13306        "
13307        .unindent(),
13308    );
13309
13310    let mocked_response = lsp::SignatureHelp {
13311        signatures: vec![lsp::SignatureInformation {
13312            label: "fn sample(param1: u8, param2: u8)".to_string(),
13313            documentation: None,
13314            parameters: Some(vec![
13315                lsp::ParameterInformation {
13316                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13317                    documentation: None,
13318                },
13319                lsp::ParameterInformation {
13320                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13321                    documentation: None,
13322                },
13323            ]),
13324            active_parameter: None,
13325        }],
13326        active_signature: Some(0),
13327        active_parameter: Some(0),
13328    };
13329    handle_signature_help_request(&mut cx, mocked_response).await;
13330
13331    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13332        .await;
13333
13334    cx.editor(|editor, _, _| {
13335        let signature_help_state = editor.signature_help_state.popover().cloned();
13336        let signature = signature_help_state.unwrap();
13337        assert_eq!(
13338            signature.signatures[signature.current_signature].label,
13339            "fn sample(param1: u8, param2: u8)"
13340        );
13341    });
13342}
13343
13344#[gpui::test]
13345async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13346    init_test(cx, |_| {});
13347
13348    cx.update(|cx| {
13349        cx.update_global::<SettingsStore, _>(|settings, cx| {
13350            settings.update_user_settings(cx, |settings| {
13351                settings.editor.auto_signature_help = Some(false);
13352                settings.editor.show_signature_help_after_edits = Some(false);
13353            });
13354        });
13355    });
13356
13357    let mut cx = EditorLspTestContext::new_rust(
13358        lsp::ServerCapabilities {
13359            signature_help_provider: Some(lsp::SignatureHelpOptions {
13360                ..Default::default()
13361            }),
13362            ..Default::default()
13363        },
13364        cx,
13365    )
13366    .await;
13367
13368    let language = Language::new(
13369        LanguageConfig {
13370            name: "Rust".into(),
13371            brackets: BracketPairConfig {
13372                pairs: vec![
13373                    BracketPair {
13374                        start: "{".to_string(),
13375                        end: "}".to_string(),
13376                        close: true,
13377                        surround: true,
13378                        newline: true,
13379                    },
13380                    BracketPair {
13381                        start: "(".to_string(),
13382                        end: ")".to_string(),
13383                        close: true,
13384                        surround: true,
13385                        newline: true,
13386                    },
13387                    BracketPair {
13388                        start: "/*".to_string(),
13389                        end: " */".to_string(),
13390                        close: true,
13391                        surround: true,
13392                        newline: true,
13393                    },
13394                    BracketPair {
13395                        start: "[".to_string(),
13396                        end: "]".to_string(),
13397                        close: false,
13398                        surround: false,
13399                        newline: true,
13400                    },
13401                    BracketPair {
13402                        start: "\"".to_string(),
13403                        end: "\"".to_string(),
13404                        close: true,
13405                        surround: true,
13406                        newline: false,
13407                    },
13408                    BracketPair {
13409                        start: "<".to_string(),
13410                        end: ">".to_string(),
13411                        close: false,
13412                        surround: true,
13413                        newline: true,
13414                    },
13415                ],
13416                ..Default::default()
13417            },
13418            autoclose_before: "})]".to_string(),
13419            ..Default::default()
13420        },
13421        Some(tree_sitter_rust::LANGUAGE.into()),
13422    );
13423    let language = Arc::new(language);
13424
13425    cx.language_registry().add(language.clone());
13426    cx.update_buffer(|buffer, cx| {
13427        buffer.set_language(Some(language), cx);
13428    });
13429
13430    // Ensure that signature_help is not called when no signature help is enabled.
13431    cx.set_state(
13432        &r#"
13433            fn main() {
13434                sampleˇ
13435            }
13436        "#
13437        .unindent(),
13438    );
13439    cx.update_editor(|editor, window, cx| {
13440        editor.handle_input("(", window, cx);
13441    });
13442    cx.assert_editor_state(
13443        &"
13444            fn main() {
13445                sample(ˇ)
13446            }
13447        "
13448        .unindent(),
13449    );
13450    cx.editor(|editor, _, _| {
13451        assert!(editor.signature_help_state.task().is_none());
13452    });
13453
13454    let mocked_response = lsp::SignatureHelp {
13455        signatures: vec![lsp::SignatureInformation {
13456            label: "fn sample(param1: u8, param2: u8)".to_string(),
13457            documentation: None,
13458            parameters: Some(vec![
13459                lsp::ParameterInformation {
13460                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13461                    documentation: None,
13462                },
13463                lsp::ParameterInformation {
13464                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13465                    documentation: None,
13466                },
13467            ]),
13468            active_parameter: None,
13469        }],
13470        active_signature: Some(0),
13471        active_parameter: Some(0),
13472    };
13473
13474    // Ensure that signature_help is called when enabled afte edits
13475    cx.update(|_, cx| {
13476        cx.update_global::<SettingsStore, _>(|settings, cx| {
13477            settings.update_user_settings(cx, |settings| {
13478                settings.editor.auto_signature_help = Some(false);
13479                settings.editor.show_signature_help_after_edits = Some(true);
13480            });
13481        });
13482    });
13483    cx.set_state(
13484        &r#"
13485            fn main() {
13486                sampleˇ
13487            }
13488        "#
13489        .unindent(),
13490    );
13491    cx.update_editor(|editor, window, cx| {
13492        editor.handle_input("(", window, cx);
13493    });
13494    cx.assert_editor_state(
13495        &"
13496            fn main() {
13497                sample(ˇ)
13498            }
13499        "
13500        .unindent(),
13501    );
13502    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13503    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13504        .await;
13505    cx.update_editor(|editor, _, _| {
13506        let signature_help_state = editor.signature_help_state.popover().cloned();
13507        assert!(signature_help_state.is_some());
13508        let signature = signature_help_state.unwrap();
13509        assert_eq!(
13510            signature.signatures[signature.current_signature].label,
13511            "fn sample(param1: u8, param2: u8)"
13512        );
13513        editor.signature_help_state = SignatureHelpState::default();
13514    });
13515
13516    // Ensure that signature_help is called when auto signature help override is enabled
13517    cx.update(|_, cx| {
13518        cx.update_global::<SettingsStore, _>(|settings, cx| {
13519            settings.update_user_settings(cx, |settings| {
13520                settings.editor.auto_signature_help = Some(true);
13521                settings.editor.show_signature_help_after_edits = Some(false);
13522            });
13523        });
13524    });
13525    cx.set_state(
13526        &r#"
13527            fn main() {
13528                sampleˇ
13529            }
13530        "#
13531        .unindent(),
13532    );
13533    cx.update_editor(|editor, window, cx| {
13534        editor.handle_input("(", window, cx);
13535    });
13536    cx.assert_editor_state(
13537        &"
13538            fn main() {
13539                sample(ˇ)
13540            }
13541        "
13542        .unindent(),
13543    );
13544    handle_signature_help_request(&mut cx, mocked_response).await;
13545    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13546        .await;
13547    cx.editor(|editor, _, _| {
13548        let signature_help_state = editor.signature_help_state.popover().cloned();
13549        assert!(signature_help_state.is_some());
13550        let signature = signature_help_state.unwrap();
13551        assert_eq!(
13552            signature.signatures[signature.current_signature].label,
13553            "fn sample(param1: u8, param2: u8)"
13554        );
13555    });
13556}
13557
13558#[gpui::test]
13559async fn test_signature_help(cx: &mut TestAppContext) {
13560    init_test(cx, |_| {});
13561    cx.update(|cx| {
13562        cx.update_global::<SettingsStore, _>(|settings, cx| {
13563            settings.update_user_settings(cx, |settings| {
13564                settings.editor.auto_signature_help = Some(true);
13565            });
13566        });
13567    });
13568
13569    let mut cx = EditorLspTestContext::new_rust(
13570        lsp::ServerCapabilities {
13571            signature_help_provider: Some(lsp::SignatureHelpOptions {
13572                ..Default::default()
13573            }),
13574            ..Default::default()
13575        },
13576        cx,
13577    )
13578    .await;
13579
13580    // A test that directly calls `show_signature_help`
13581    cx.update_editor(|editor, window, cx| {
13582        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13583    });
13584
13585    let mocked_response = lsp::SignatureHelp {
13586        signatures: vec![lsp::SignatureInformation {
13587            label: "fn sample(param1: u8, param2: u8)".to_string(),
13588            documentation: None,
13589            parameters: Some(vec![
13590                lsp::ParameterInformation {
13591                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13592                    documentation: None,
13593                },
13594                lsp::ParameterInformation {
13595                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13596                    documentation: None,
13597                },
13598            ]),
13599            active_parameter: None,
13600        }],
13601        active_signature: Some(0),
13602        active_parameter: Some(0),
13603    };
13604    handle_signature_help_request(&mut cx, mocked_response).await;
13605
13606    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13607        .await;
13608
13609    cx.editor(|editor, _, _| {
13610        let signature_help_state = editor.signature_help_state.popover().cloned();
13611        assert!(signature_help_state.is_some());
13612        let signature = signature_help_state.unwrap();
13613        assert_eq!(
13614            signature.signatures[signature.current_signature].label,
13615            "fn sample(param1: u8, param2: u8)"
13616        );
13617    });
13618
13619    // When exiting outside from inside the brackets, `signature_help` is closed.
13620    cx.set_state(indoc! {"
13621        fn main() {
13622            sample(ˇ);
13623        }
13624
13625        fn sample(param1: u8, param2: u8) {}
13626    "});
13627
13628    cx.update_editor(|editor, window, cx| {
13629        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13630            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13631        });
13632    });
13633
13634    let mocked_response = lsp::SignatureHelp {
13635        signatures: Vec::new(),
13636        active_signature: None,
13637        active_parameter: None,
13638    };
13639    handle_signature_help_request(&mut cx, mocked_response).await;
13640
13641    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13642        .await;
13643
13644    cx.editor(|editor, _, _| {
13645        assert!(!editor.signature_help_state.is_shown());
13646    });
13647
13648    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13649    cx.set_state(indoc! {"
13650        fn main() {
13651            sample(ˇ);
13652        }
13653
13654        fn sample(param1: u8, param2: u8) {}
13655    "});
13656
13657    let mocked_response = lsp::SignatureHelp {
13658        signatures: vec![lsp::SignatureInformation {
13659            label: "fn sample(param1: u8, param2: u8)".to_string(),
13660            documentation: None,
13661            parameters: Some(vec![
13662                lsp::ParameterInformation {
13663                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13664                    documentation: None,
13665                },
13666                lsp::ParameterInformation {
13667                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13668                    documentation: None,
13669                },
13670            ]),
13671            active_parameter: None,
13672        }],
13673        active_signature: Some(0),
13674        active_parameter: Some(0),
13675    };
13676    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13677    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13678        .await;
13679    cx.editor(|editor, _, _| {
13680        assert!(editor.signature_help_state.is_shown());
13681    });
13682
13683    // Restore the popover with more parameter input
13684    cx.set_state(indoc! {"
13685        fn main() {
13686            sample(param1, param2ˇ);
13687        }
13688
13689        fn sample(param1: u8, param2: u8) {}
13690    "});
13691
13692    let mocked_response = lsp::SignatureHelp {
13693        signatures: vec![lsp::SignatureInformation {
13694            label: "fn sample(param1: u8, param2: u8)".to_string(),
13695            documentation: None,
13696            parameters: Some(vec![
13697                lsp::ParameterInformation {
13698                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13699                    documentation: None,
13700                },
13701                lsp::ParameterInformation {
13702                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13703                    documentation: None,
13704                },
13705            ]),
13706            active_parameter: None,
13707        }],
13708        active_signature: Some(0),
13709        active_parameter: Some(1),
13710    };
13711    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13712    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13713        .await;
13714
13715    // When selecting a range, the popover is gone.
13716    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13717    cx.update_editor(|editor, window, cx| {
13718        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13719            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13720        })
13721    });
13722    cx.assert_editor_state(indoc! {"
13723        fn main() {
13724            sample(param1, «ˇparam2»);
13725        }
13726
13727        fn sample(param1: u8, param2: u8) {}
13728    "});
13729    cx.editor(|editor, _, _| {
13730        assert!(!editor.signature_help_state.is_shown());
13731    });
13732
13733    // When unselecting again, the popover is back if within the brackets.
13734    cx.update_editor(|editor, window, cx| {
13735        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13736            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13737        })
13738    });
13739    cx.assert_editor_state(indoc! {"
13740        fn main() {
13741            sample(param1, ˇparam2);
13742        }
13743
13744        fn sample(param1: u8, param2: u8) {}
13745    "});
13746    handle_signature_help_request(&mut cx, mocked_response).await;
13747    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13748        .await;
13749    cx.editor(|editor, _, _| {
13750        assert!(editor.signature_help_state.is_shown());
13751    });
13752
13753    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13754    cx.update_editor(|editor, window, cx| {
13755        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13756            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13757            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13758        })
13759    });
13760    cx.assert_editor_state(indoc! {"
13761        fn main() {
13762            sample(param1, ˇparam2);
13763        }
13764
13765        fn sample(param1: u8, param2: u8) {}
13766    "});
13767
13768    let mocked_response = lsp::SignatureHelp {
13769        signatures: vec![lsp::SignatureInformation {
13770            label: "fn sample(param1: u8, param2: u8)".to_string(),
13771            documentation: None,
13772            parameters: Some(vec![
13773                lsp::ParameterInformation {
13774                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13775                    documentation: None,
13776                },
13777                lsp::ParameterInformation {
13778                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13779                    documentation: None,
13780                },
13781            ]),
13782            active_parameter: None,
13783        }],
13784        active_signature: Some(0),
13785        active_parameter: Some(1),
13786    };
13787    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13788    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13789        .await;
13790    cx.update_editor(|editor, _, cx| {
13791        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13792    });
13793    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13794        .await;
13795    cx.update_editor(|editor, window, cx| {
13796        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13797            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13798        })
13799    });
13800    cx.assert_editor_state(indoc! {"
13801        fn main() {
13802            sample(param1, «ˇparam2»);
13803        }
13804
13805        fn sample(param1: u8, param2: u8) {}
13806    "});
13807    cx.update_editor(|editor, window, cx| {
13808        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13809            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13810        })
13811    });
13812    cx.assert_editor_state(indoc! {"
13813        fn main() {
13814            sample(param1, ˇparam2);
13815        }
13816
13817        fn sample(param1: u8, param2: u8) {}
13818    "});
13819    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13820        .await;
13821}
13822
13823#[gpui::test]
13824async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13825    init_test(cx, |_| {});
13826
13827    let mut cx = EditorLspTestContext::new_rust(
13828        lsp::ServerCapabilities {
13829            signature_help_provider: Some(lsp::SignatureHelpOptions {
13830                ..Default::default()
13831            }),
13832            ..Default::default()
13833        },
13834        cx,
13835    )
13836    .await;
13837
13838    cx.set_state(indoc! {"
13839        fn main() {
13840            overloadedˇ
13841        }
13842    "});
13843
13844    cx.update_editor(|editor, window, cx| {
13845        editor.handle_input("(", window, cx);
13846        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13847    });
13848
13849    // Mock response with 3 signatures
13850    let mocked_response = lsp::SignatureHelp {
13851        signatures: vec![
13852            lsp::SignatureInformation {
13853                label: "fn overloaded(x: i32)".to_string(),
13854                documentation: None,
13855                parameters: Some(vec![lsp::ParameterInformation {
13856                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13857                    documentation: None,
13858                }]),
13859                active_parameter: None,
13860            },
13861            lsp::SignatureInformation {
13862                label: "fn overloaded(x: i32, y: i32)".to_string(),
13863                documentation: None,
13864                parameters: Some(vec![
13865                    lsp::ParameterInformation {
13866                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13867                        documentation: None,
13868                    },
13869                    lsp::ParameterInformation {
13870                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13871                        documentation: None,
13872                    },
13873                ]),
13874                active_parameter: None,
13875            },
13876            lsp::SignatureInformation {
13877                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13878                documentation: None,
13879                parameters: Some(vec![
13880                    lsp::ParameterInformation {
13881                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13882                        documentation: None,
13883                    },
13884                    lsp::ParameterInformation {
13885                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13886                        documentation: None,
13887                    },
13888                    lsp::ParameterInformation {
13889                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13890                        documentation: None,
13891                    },
13892                ]),
13893                active_parameter: None,
13894            },
13895        ],
13896        active_signature: Some(1),
13897        active_parameter: Some(0),
13898    };
13899    handle_signature_help_request(&mut cx, mocked_response).await;
13900
13901    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13902        .await;
13903
13904    // Verify we have multiple signatures and the right one is selected
13905    cx.editor(|editor, _, _| {
13906        let popover = editor.signature_help_state.popover().cloned().unwrap();
13907        assert_eq!(popover.signatures.len(), 3);
13908        // active_signature was 1, so that should be the current
13909        assert_eq!(popover.current_signature, 1);
13910        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13911        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13912        assert_eq!(
13913            popover.signatures[2].label,
13914            "fn overloaded(x: i32, y: i32, z: i32)"
13915        );
13916    });
13917
13918    // Test navigation functionality
13919    cx.update_editor(|editor, window, cx| {
13920        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13921    });
13922
13923    cx.editor(|editor, _, _| {
13924        let popover = editor.signature_help_state.popover().cloned().unwrap();
13925        assert_eq!(popover.current_signature, 2);
13926    });
13927
13928    // Test wrap around
13929    cx.update_editor(|editor, window, cx| {
13930        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13931    });
13932
13933    cx.editor(|editor, _, _| {
13934        let popover = editor.signature_help_state.popover().cloned().unwrap();
13935        assert_eq!(popover.current_signature, 0);
13936    });
13937
13938    // Test previous navigation
13939    cx.update_editor(|editor, window, cx| {
13940        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13941    });
13942
13943    cx.editor(|editor, _, _| {
13944        let popover = editor.signature_help_state.popover().cloned().unwrap();
13945        assert_eq!(popover.current_signature, 2);
13946    });
13947}
13948
13949#[gpui::test]
13950async fn test_completion_mode(cx: &mut TestAppContext) {
13951    init_test(cx, |_| {});
13952    let mut cx = EditorLspTestContext::new_rust(
13953        lsp::ServerCapabilities {
13954            completion_provider: Some(lsp::CompletionOptions {
13955                resolve_provider: Some(true),
13956                ..Default::default()
13957            }),
13958            ..Default::default()
13959        },
13960        cx,
13961    )
13962    .await;
13963
13964    struct Run {
13965        run_description: &'static str,
13966        initial_state: String,
13967        buffer_marked_text: String,
13968        completion_label: &'static str,
13969        completion_text: &'static str,
13970        expected_with_insert_mode: String,
13971        expected_with_replace_mode: String,
13972        expected_with_replace_subsequence_mode: String,
13973        expected_with_replace_suffix_mode: String,
13974    }
13975
13976    let runs = [
13977        Run {
13978            run_description: "Start of word matches completion text",
13979            initial_state: "before ediˇ after".into(),
13980            buffer_marked_text: "before <edi|> after".into(),
13981            completion_label: "editor",
13982            completion_text: "editor",
13983            expected_with_insert_mode: "before editorˇ after".into(),
13984            expected_with_replace_mode: "before editorˇ after".into(),
13985            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13986            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13987        },
13988        Run {
13989            run_description: "Accept same text at the middle of the word",
13990            initial_state: "before ediˇtor after".into(),
13991            buffer_marked_text: "before <edi|tor> after".into(),
13992            completion_label: "editor",
13993            completion_text: "editor",
13994            expected_with_insert_mode: "before editorˇtor after".into(),
13995            expected_with_replace_mode: "before editorˇ after".into(),
13996            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13997            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13998        },
13999        Run {
14000            run_description: "End of word matches completion text -- cursor at end",
14001            initial_state: "before torˇ after".into(),
14002            buffer_marked_text: "before <tor|> after".into(),
14003            completion_label: "editor",
14004            completion_text: "editor",
14005            expected_with_insert_mode: "before editorˇ after".into(),
14006            expected_with_replace_mode: "before editorˇ after".into(),
14007            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14008            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14009        },
14010        Run {
14011            run_description: "End of word matches completion text -- cursor at start",
14012            initial_state: "before ˇtor after".into(),
14013            buffer_marked_text: "before <|tor> after".into(),
14014            completion_label: "editor",
14015            completion_text: "editor",
14016            expected_with_insert_mode: "before editorˇtor after".into(),
14017            expected_with_replace_mode: "before editorˇ after".into(),
14018            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14019            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14020        },
14021        Run {
14022            run_description: "Prepend text containing whitespace",
14023            initial_state: "pˇfield: bool".into(),
14024            buffer_marked_text: "<p|field>: bool".into(),
14025            completion_label: "pub ",
14026            completion_text: "pub ",
14027            expected_with_insert_mode: "pub ˇfield: bool".into(),
14028            expected_with_replace_mode: "pub ˇ: bool".into(),
14029            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
14030            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
14031        },
14032        Run {
14033            run_description: "Add element to start of list",
14034            initial_state: "[element_ˇelement_2]".into(),
14035            buffer_marked_text: "[<element_|element_2>]".into(),
14036            completion_label: "element_1",
14037            completion_text: "element_1",
14038            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
14039            expected_with_replace_mode: "[element_1ˇ]".into(),
14040            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
14041            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
14042        },
14043        Run {
14044            run_description: "Add element to start of list -- first and second elements are equal",
14045            initial_state: "[elˇelement]".into(),
14046            buffer_marked_text: "[<el|element>]".into(),
14047            completion_label: "element",
14048            completion_text: "element",
14049            expected_with_insert_mode: "[elementˇelement]".into(),
14050            expected_with_replace_mode: "[elementˇ]".into(),
14051            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
14052            expected_with_replace_suffix_mode: "[elementˇ]".into(),
14053        },
14054        Run {
14055            run_description: "Ends with matching suffix",
14056            initial_state: "SubˇError".into(),
14057            buffer_marked_text: "<Sub|Error>".into(),
14058            completion_label: "SubscriptionError",
14059            completion_text: "SubscriptionError",
14060            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
14061            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14062            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14063            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
14064        },
14065        Run {
14066            run_description: "Suffix is a subsequence -- contiguous",
14067            initial_state: "SubˇErr".into(),
14068            buffer_marked_text: "<Sub|Err>".into(),
14069            completion_label: "SubscriptionError",
14070            completion_text: "SubscriptionError",
14071            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
14072            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14073            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14074            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
14075        },
14076        Run {
14077            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
14078            initial_state: "Suˇscrirr".into(),
14079            buffer_marked_text: "<Su|scrirr>".into(),
14080            completion_label: "SubscriptionError",
14081            completion_text: "SubscriptionError",
14082            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
14083            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
14084            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
14085            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
14086        },
14087        Run {
14088            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
14089            initial_state: "foo(indˇix)".into(),
14090            buffer_marked_text: "foo(<ind|ix>)".into(),
14091            completion_label: "node_index",
14092            completion_text: "node_index",
14093            expected_with_insert_mode: "foo(node_indexˇix)".into(),
14094            expected_with_replace_mode: "foo(node_indexˇ)".into(),
14095            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
14096            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
14097        },
14098        Run {
14099            run_description: "Replace range ends before cursor - should extend to cursor",
14100            initial_state: "before editˇo after".into(),
14101            buffer_marked_text: "before <{ed}>it|o after".into(),
14102            completion_label: "editor",
14103            completion_text: "editor",
14104            expected_with_insert_mode: "before editorˇo after".into(),
14105            expected_with_replace_mode: "before editorˇo after".into(),
14106            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14107            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14108        },
14109        Run {
14110            run_description: "Uses label for suffix matching",
14111            initial_state: "before ediˇtor after".into(),
14112            buffer_marked_text: "before <edi|tor> after".into(),
14113            completion_label: "editor",
14114            completion_text: "editor()",
14115            expected_with_insert_mode: "before editor()ˇtor after".into(),
14116            expected_with_replace_mode: "before editor()ˇ after".into(),
14117            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14118            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14119        },
14120        Run {
14121            run_description: "Case insensitive subsequence and suffix matching",
14122            initial_state: "before EDiˇtoR after".into(),
14123            buffer_marked_text: "before <EDi|toR> after".into(),
14124            completion_label: "editor",
14125            completion_text: "editor",
14126            expected_with_insert_mode: "before editorˇtoR after".into(),
14127            expected_with_replace_mode: "before editorˇ after".into(),
14128            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14129            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14130        },
14131    ];
14132
14133    for run in runs {
14134        let run_variations = [
14135            (LspInsertMode::Insert, run.expected_with_insert_mode),
14136            (LspInsertMode::Replace, run.expected_with_replace_mode),
14137            (
14138                LspInsertMode::ReplaceSubsequence,
14139                run.expected_with_replace_subsequence_mode,
14140            ),
14141            (
14142                LspInsertMode::ReplaceSuffix,
14143                run.expected_with_replace_suffix_mode,
14144            ),
14145        ];
14146
14147        for (lsp_insert_mode, expected_text) in run_variations {
14148            eprintln!(
14149                "run = {:?}, mode = {lsp_insert_mode:.?}",
14150                run.run_description,
14151            );
14152
14153            update_test_language_settings(&mut cx, |settings| {
14154                settings.defaults.completions = Some(CompletionSettingsContent {
14155                    lsp_insert_mode: Some(lsp_insert_mode),
14156                    words: Some(WordsCompletionMode::Disabled),
14157                    words_min_length: Some(0),
14158                    ..Default::default()
14159                });
14160            });
14161
14162            cx.set_state(&run.initial_state);
14163            cx.update_editor(|editor, window, cx| {
14164                editor.show_completions(&ShowCompletions, window, cx);
14165            });
14166
14167            let counter = Arc::new(AtomicUsize::new(0));
14168            handle_completion_request_with_insert_and_replace(
14169                &mut cx,
14170                &run.buffer_marked_text,
14171                vec![(run.completion_label, run.completion_text)],
14172                counter.clone(),
14173            )
14174            .await;
14175            cx.condition(|editor, _| editor.context_menu_visible())
14176                .await;
14177            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14178
14179            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14180                editor
14181                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14182                    .unwrap()
14183            });
14184            cx.assert_editor_state(&expected_text);
14185            handle_resolve_completion_request(&mut cx, None).await;
14186            apply_additional_edits.await.unwrap();
14187        }
14188    }
14189}
14190
14191#[gpui::test]
14192async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14193    init_test(cx, |_| {});
14194    let mut cx = EditorLspTestContext::new_rust(
14195        lsp::ServerCapabilities {
14196            completion_provider: Some(lsp::CompletionOptions {
14197                resolve_provider: Some(true),
14198                ..Default::default()
14199            }),
14200            ..Default::default()
14201        },
14202        cx,
14203    )
14204    .await;
14205
14206    let initial_state = "SubˇError";
14207    let buffer_marked_text = "<Sub|Error>";
14208    let completion_text = "SubscriptionError";
14209    let expected_with_insert_mode = "SubscriptionErrorˇError";
14210    let expected_with_replace_mode = "SubscriptionErrorˇ";
14211
14212    update_test_language_settings(&mut cx, |settings| {
14213        settings.defaults.completions = Some(CompletionSettingsContent {
14214            words: Some(WordsCompletionMode::Disabled),
14215            words_min_length: Some(0),
14216            // set the opposite here to ensure that the action is overriding the default behavior
14217            lsp_insert_mode: Some(LspInsertMode::Insert),
14218            ..Default::default()
14219        });
14220    });
14221
14222    cx.set_state(initial_state);
14223    cx.update_editor(|editor, window, cx| {
14224        editor.show_completions(&ShowCompletions, window, cx);
14225    });
14226
14227    let counter = Arc::new(AtomicUsize::new(0));
14228    handle_completion_request_with_insert_and_replace(
14229        &mut cx,
14230        buffer_marked_text,
14231        vec![(completion_text, completion_text)],
14232        counter.clone(),
14233    )
14234    .await;
14235    cx.condition(|editor, _| editor.context_menu_visible())
14236        .await;
14237    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14238
14239    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14240        editor
14241            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14242            .unwrap()
14243    });
14244    cx.assert_editor_state(expected_with_replace_mode);
14245    handle_resolve_completion_request(&mut cx, None).await;
14246    apply_additional_edits.await.unwrap();
14247
14248    update_test_language_settings(&mut cx, |settings| {
14249        settings.defaults.completions = Some(CompletionSettingsContent {
14250            words: Some(WordsCompletionMode::Disabled),
14251            words_min_length: Some(0),
14252            // set the opposite here to ensure that the action is overriding the default behavior
14253            lsp_insert_mode: Some(LspInsertMode::Replace),
14254            ..Default::default()
14255        });
14256    });
14257
14258    cx.set_state(initial_state);
14259    cx.update_editor(|editor, window, cx| {
14260        editor.show_completions(&ShowCompletions, window, cx);
14261    });
14262    handle_completion_request_with_insert_and_replace(
14263        &mut cx,
14264        buffer_marked_text,
14265        vec![(completion_text, completion_text)],
14266        counter.clone(),
14267    )
14268    .await;
14269    cx.condition(|editor, _| editor.context_menu_visible())
14270        .await;
14271    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14272
14273    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14274        editor
14275            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14276            .unwrap()
14277    });
14278    cx.assert_editor_state(expected_with_insert_mode);
14279    handle_resolve_completion_request(&mut cx, None).await;
14280    apply_additional_edits.await.unwrap();
14281}
14282
14283#[gpui::test]
14284async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14285    init_test(cx, |_| {});
14286    let mut cx = EditorLspTestContext::new_rust(
14287        lsp::ServerCapabilities {
14288            completion_provider: Some(lsp::CompletionOptions {
14289                resolve_provider: Some(true),
14290                ..Default::default()
14291            }),
14292            ..Default::default()
14293        },
14294        cx,
14295    )
14296    .await;
14297
14298    // scenario: surrounding text matches completion text
14299    let completion_text = "to_offset";
14300    let initial_state = indoc! {"
14301        1. buf.to_offˇsuffix
14302        2. buf.to_offˇsuf
14303        3. buf.to_offˇfix
14304        4. buf.to_offˇ
14305        5. into_offˇensive
14306        6. ˇsuffix
14307        7. let ˇ //
14308        8. aaˇzz
14309        9. buf.to_off«zzzzzˇ»suffix
14310        10. buf.«ˇzzzzz»suffix
14311        11. to_off«ˇzzzzz»
14312
14313        buf.to_offˇsuffix  // newest cursor
14314    "};
14315    let completion_marked_buffer = indoc! {"
14316        1. buf.to_offsuffix
14317        2. buf.to_offsuf
14318        3. buf.to_offfix
14319        4. buf.to_off
14320        5. into_offensive
14321        6. suffix
14322        7. let  //
14323        8. aazz
14324        9. buf.to_offzzzzzsuffix
14325        10. buf.zzzzzsuffix
14326        11. to_offzzzzz
14327
14328        buf.<to_off|suffix>  // newest cursor
14329    "};
14330    let expected = indoc! {"
14331        1. buf.to_offsetˇ
14332        2. buf.to_offsetˇsuf
14333        3. buf.to_offsetˇfix
14334        4. buf.to_offsetˇ
14335        5. into_offsetˇensive
14336        6. to_offsetˇsuffix
14337        7. let to_offsetˇ //
14338        8. aato_offsetˇzz
14339        9. buf.to_offsetˇ
14340        10. buf.to_offsetˇsuffix
14341        11. to_offsetˇ
14342
14343        buf.to_offsetˇ  // newest cursor
14344    "};
14345    cx.set_state(initial_state);
14346    cx.update_editor(|editor, window, cx| {
14347        editor.show_completions(&ShowCompletions, window, cx);
14348    });
14349    handle_completion_request_with_insert_and_replace(
14350        &mut cx,
14351        completion_marked_buffer,
14352        vec![(completion_text, completion_text)],
14353        Arc::new(AtomicUsize::new(0)),
14354    )
14355    .await;
14356    cx.condition(|editor, _| editor.context_menu_visible())
14357        .await;
14358    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14359        editor
14360            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14361            .unwrap()
14362    });
14363    cx.assert_editor_state(expected);
14364    handle_resolve_completion_request(&mut cx, None).await;
14365    apply_additional_edits.await.unwrap();
14366
14367    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14368    let completion_text = "foo_and_bar";
14369    let initial_state = indoc! {"
14370        1. ooanbˇ
14371        2. zooanbˇ
14372        3. ooanbˇz
14373        4. zooanbˇz
14374        5. ooanˇ
14375        6. oanbˇ
14376
14377        ooanbˇ
14378    "};
14379    let completion_marked_buffer = indoc! {"
14380        1. ooanb
14381        2. zooanb
14382        3. ooanbz
14383        4. zooanbz
14384        5. ooan
14385        6. oanb
14386
14387        <ooanb|>
14388    "};
14389    let expected = indoc! {"
14390        1. foo_and_barˇ
14391        2. zfoo_and_barˇ
14392        3. foo_and_barˇz
14393        4. zfoo_and_barˇz
14394        5. ooanfoo_and_barˇ
14395        6. oanbfoo_and_barˇ
14396
14397        foo_and_barˇ
14398    "};
14399    cx.set_state(initial_state);
14400    cx.update_editor(|editor, window, cx| {
14401        editor.show_completions(&ShowCompletions, window, cx);
14402    });
14403    handle_completion_request_with_insert_and_replace(
14404        &mut cx,
14405        completion_marked_buffer,
14406        vec![(completion_text, completion_text)],
14407        Arc::new(AtomicUsize::new(0)),
14408    )
14409    .await;
14410    cx.condition(|editor, _| editor.context_menu_visible())
14411        .await;
14412    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14413        editor
14414            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14415            .unwrap()
14416    });
14417    cx.assert_editor_state(expected);
14418    handle_resolve_completion_request(&mut cx, None).await;
14419    apply_additional_edits.await.unwrap();
14420
14421    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14422    // (expects the same as if it was inserted at the end)
14423    let completion_text = "foo_and_bar";
14424    let initial_state = indoc! {"
14425        1. ooˇanb
14426        2. zooˇanb
14427        3. ooˇanbz
14428        4. zooˇanbz
14429
14430        ooˇanb
14431    "};
14432    let completion_marked_buffer = indoc! {"
14433        1. ooanb
14434        2. zooanb
14435        3. ooanbz
14436        4. zooanbz
14437
14438        <oo|anb>
14439    "};
14440    let expected = indoc! {"
14441        1. foo_and_barˇ
14442        2. zfoo_and_barˇ
14443        3. foo_and_barˇz
14444        4. zfoo_and_barˇz
14445
14446        foo_and_barˇ
14447    "};
14448    cx.set_state(initial_state);
14449    cx.update_editor(|editor, window, cx| {
14450        editor.show_completions(&ShowCompletions, window, cx);
14451    });
14452    handle_completion_request_with_insert_and_replace(
14453        &mut cx,
14454        completion_marked_buffer,
14455        vec![(completion_text, completion_text)],
14456        Arc::new(AtomicUsize::new(0)),
14457    )
14458    .await;
14459    cx.condition(|editor, _| editor.context_menu_visible())
14460        .await;
14461    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14462        editor
14463            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14464            .unwrap()
14465    });
14466    cx.assert_editor_state(expected);
14467    handle_resolve_completion_request(&mut cx, None).await;
14468    apply_additional_edits.await.unwrap();
14469}
14470
14471// This used to crash
14472#[gpui::test]
14473async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14474    init_test(cx, |_| {});
14475
14476    let buffer_text = indoc! {"
14477        fn main() {
14478            10.satu;
14479
14480            //
14481            // separate cursors so they open in different excerpts (manually reproducible)
14482            //
14483
14484            10.satu20;
14485        }
14486    "};
14487    let multibuffer_text_with_selections = indoc! {"
14488        fn main() {
14489            10.satuˇ;
14490
14491            //
14492
14493            //
14494
14495            10.satuˇ20;
14496        }
14497    "};
14498    let expected_multibuffer = indoc! {"
14499        fn main() {
14500            10.saturating_sub()ˇ;
14501
14502            //
14503
14504            //
14505
14506            10.saturating_sub()ˇ;
14507        }
14508    "};
14509
14510    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14511    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14512
14513    let fs = FakeFs::new(cx.executor());
14514    fs.insert_tree(
14515        path!("/a"),
14516        json!({
14517            "main.rs": buffer_text,
14518        }),
14519    )
14520    .await;
14521
14522    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14523    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14524    language_registry.add(rust_lang());
14525    let mut fake_servers = language_registry.register_fake_lsp(
14526        "Rust",
14527        FakeLspAdapter {
14528            capabilities: lsp::ServerCapabilities {
14529                completion_provider: Some(lsp::CompletionOptions {
14530                    resolve_provider: None,
14531                    ..lsp::CompletionOptions::default()
14532                }),
14533                ..lsp::ServerCapabilities::default()
14534            },
14535            ..FakeLspAdapter::default()
14536        },
14537    );
14538    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14539    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14540    let buffer = project
14541        .update(cx, |project, cx| {
14542            project.open_local_buffer(path!("/a/main.rs"), cx)
14543        })
14544        .await
14545        .unwrap();
14546
14547    let multi_buffer = cx.new(|cx| {
14548        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14549        multi_buffer.push_excerpts(
14550            buffer.clone(),
14551            [ExcerptRange::new(0..first_excerpt_end)],
14552            cx,
14553        );
14554        multi_buffer.push_excerpts(
14555            buffer.clone(),
14556            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14557            cx,
14558        );
14559        multi_buffer
14560    });
14561
14562    let editor = workspace
14563        .update(cx, |_, window, cx| {
14564            cx.new(|cx| {
14565                Editor::new(
14566                    EditorMode::Full {
14567                        scale_ui_elements_with_buffer_font_size: false,
14568                        show_active_line_background: false,
14569                        sizing_behavior: SizingBehavior::Default,
14570                    },
14571                    multi_buffer.clone(),
14572                    Some(project.clone()),
14573                    window,
14574                    cx,
14575                )
14576            })
14577        })
14578        .unwrap();
14579
14580    let pane = workspace
14581        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14582        .unwrap();
14583    pane.update_in(cx, |pane, window, cx| {
14584        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14585    });
14586
14587    let fake_server = fake_servers.next().await.unwrap();
14588
14589    editor.update_in(cx, |editor, window, cx| {
14590        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14591            s.select_ranges([
14592                Point::new(1, 11)..Point::new(1, 11),
14593                Point::new(7, 11)..Point::new(7, 11),
14594            ])
14595        });
14596
14597        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14598    });
14599
14600    editor.update_in(cx, |editor, window, cx| {
14601        editor.show_completions(&ShowCompletions, window, cx);
14602    });
14603
14604    fake_server
14605        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14606            let completion_item = lsp::CompletionItem {
14607                label: "saturating_sub()".into(),
14608                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14609                    lsp::InsertReplaceEdit {
14610                        new_text: "saturating_sub()".to_owned(),
14611                        insert: lsp::Range::new(
14612                            lsp::Position::new(7, 7),
14613                            lsp::Position::new(7, 11),
14614                        ),
14615                        replace: lsp::Range::new(
14616                            lsp::Position::new(7, 7),
14617                            lsp::Position::new(7, 13),
14618                        ),
14619                    },
14620                )),
14621                ..lsp::CompletionItem::default()
14622            };
14623
14624            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14625        })
14626        .next()
14627        .await
14628        .unwrap();
14629
14630    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14631        .await;
14632
14633    editor
14634        .update_in(cx, |editor, window, cx| {
14635            editor
14636                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14637                .unwrap()
14638        })
14639        .await
14640        .unwrap();
14641
14642    editor.update(cx, |editor, cx| {
14643        assert_text_with_selections(editor, expected_multibuffer, cx);
14644    })
14645}
14646
14647#[gpui::test]
14648async fn test_completion(cx: &mut TestAppContext) {
14649    init_test(cx, |_| {});
14650
14651    let mut cx = EditorLspTestContext::new_rust(
14652        lsp::ServerCapabilities {
14653            completion_provider: Some(lsp::CompletionOptions {
14654                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14655                resolve_provider: Some(true),
14656                ..Default::default()
14657            }),
14658            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14659            ..Default::default()
14660        },
14661        cx,
14662    )
14663    .await;
14664    let counter = Arc::new(AtomicUsize::new(0));
14665
14666    cx.set_state(indoc! {"
14667        oneˇ
14668        two
14669        three
14670    "});
14671    cx.simulate_keystroke(".");
14672    handle_completion_request(
14673        indoc! {"
14674            one.|<>
14675            two
14676            three
14677        "},
14678        vec!["first_completion", "second_completion"],
14679        true,
14680        counter.clone(),
14681        &mut cx,
14682    )
14683    .await;
14684    cx.condition(|editor, _| editor.context_menu_visible())
14685        .await;
14686    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14687
14688    let _handler = handle_signature_help_request(
14689        &mut cx,
14690        lsp::SignatureHelp {
14691            signatures: vec![lsp::SignatureInformation {
14692                label: "test signature".to_string(),
14693                documentation: None,
14694                parameters: Some(vec![lsp::ParameterInformation {
14695                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14696                    documentation: None,
14697                }]),
14698                active_parameter: None,
14699            }],
14700            active_signature: None,
14701            active_parameter: None,
14702        },
14703    );
14704    cx.update_editor(|editor, window, cx| {
14705        assert!(
14706            !editor.signature_help_state.is_shown(),
14707            "No signature help was called for"
14708        );
14709        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14710    });
14711    cx.run_until_parked();
14712    cx.update_editor(|editor, _, _| {
14713        assert!(
14714            !editor.signature_help_state.is_shown(),
14715            "No signature help should be shown when completions menu is open"
14716        );
14717    });
14718
14719    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14720        editor.context_menu_next(&Default::default(), window, cx);
14721        editor
14722            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14723            .unwrap()
14724    });
14725    cx.assert_editor_state(indoc! {"
14726        one.second_completionˇ
14727        two
14728        three
14729    "});
14730
14731    handle_resolve_completion_request(
14732        &mut cx,
14733        Some(vec![
14734            (
14735                //This overlaps with the primary completion edit which is
14736                //misbehavior from the LSP spec, test that we filter it out
14737                indoc! {"
14738                    one.second_ˇcompletion
14739                    two
14740                    threeˇ
14741                "},
14742                "overlapping additional edit",
14743            ),
14744            (
14745                indoc! {"
14746                    one.second_completion
14747                    two
14748                    threeˇ
14749                "},
14750                "\nadditional edit",
14751            ),
14752        ]),
14753    )
14754    .await;
14755    apply_additional_edits.await.unwrap();
14756    cx.assert_editor_state(indoc! {"
14757        one.second_completionˇ
14758        two
14759        three
14760        additional edit
14761    "});
14762
14763    cx.set_state(indoc! {"
14764        one.second_completion
14765        twoˇ
14766        threeˇ
14767        additional edit
14768    "});
14769    cx.simulate_keystroke(" ");
14770    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14771    cx.simulate_keystroke("s");
14772    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14773
14774    cx.assert_editor_state(indoc! {"
14775        one.second_completion
14776        two sˇ
14777        three sˇ
14778        additional edit
14779    "});
14780    handle_completion_request(
14781        indoc! {"
14782            one.second_completion
14783            two s
14784            three <s|>
14785            additional edit
14786        "},
14787        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14788        true,
14789        counter.clone(),
14790        &mut cx,
14791    )
14792    .await;
14793    cx.condition(|editor, _| editor.context_menu_visible())
14794        .await;
14795    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14796
14797    cx.simulate_keystroke("i");
14798
14799    handle_completion_request(
14800        indoc! {"
14801            one.second_completion
14802            two si
14803            three <si|>
14804            additional edit
14805        "},
14806        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14807        true,
14808        counter.clone(),
14809        &mut cx,
14810    )
14811    .await;
14812    cx.condition(|editor, _| editor.context_menu_visible())
14813        .await;
14814    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14815
14816    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14817        editor
14818            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14819            .unwrap()
14820    });
14821    cx.assert_editor_state(indoc! {"
14822        one.second_completion
14823        two sixth_completionˇ
14824        three sixth_completionˇ
14825        additional edit
14826    "});
14827
14828    apply_additional_edits.await.unwrap();
14829
14830    update_test_language_settings(&mut cx, |settings| {
14831        settings.defaults.show_completions_on_input = Some(false);
14832    });
14833    cx.set_state("editorˇ");
14834    cx.simulate_keystroke(".");
14835    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14836    cx.simulate_keystrokes("c l o");
14837    cx.assert_editor_state("editor.cloˇ");
14838    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14839    cx.update_editor(|editor, window, cx| {
14840        editor.show_completions(&ShowCompletions, window, cx);
14841    });
14842    handle_completion_request(
14843        "editor.<clo|>",
14844        vec!["close", "clobber"],
14845        true,
14846        counter.clone(),
14847        &mut cx,
14848    )
14849    .await;
14850    cx.condition(|editor, _| editor.context_menu_visible())
14851        .await;
14852    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14853
14854    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14855        editor
14856            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14857            .unwrap()
14858    });
14859    cx.assert_editor_state("editor.clobberˇ");
14860    handle_resolve_completion_request(&mut cx, None).await;
14861    apply_additional_edits.await.unwrap();
14862}
14863
14864#[gpui::test]
14865async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
14866    init_test(cx, |_| {});
14867
14868    let fs = FakeFs::new(cx.executor());
14869    fs.insert_tree(
14870        path!("/a"),
14871        json!({
14872            "main.rs": "",
14873        }),
14874    )
14875    .await;
14876
14877    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14878    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14879    language_registry.add(rust_lang());
14880    let command_calls = Arc::new(AtomicUsize::new(0));
14881    let registered_command = "_the/command";
14882
14883    let closure_command_calls = command_calls.clone();
14884    let mut fake_servers = language_registry.register_fake_lsp(
14885        "Rust",
14886        FakeLspAdapter {
14887            capabilities: lsp::ServerCapabilities {
14888                completion_provider: Some(lsp::CompletionOptions {
14889                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14890                    ..lsp::CompletionOptions::default()
14891                }),
14892                execute_command_provider: Some(lsp::ExecuteCommandOptions {
14893                    commands: vec![registered_command.to_owned()],
14894                    ..lsp::ExecuteCommandOptions::default()
14895                }),
14896                ..lsp::ServerCapabilities::default()
14897            },
14898            initializer: Some(Box::new(move |fake_server| {
14899                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14900                    move |params, _| async move {
14901                        Ok(Some(lsp::CompletionResponse::Array(vec![
14902                            lsp::CompletionItem {
14903                                label: "registered_command".to_owned(),
14904                                text_edit: gen_text_edit(&params, ""),
14905                                command: Some(lsp::Command {
14906                                    title: registered_command.to_owned(),
14907                                    command: "_the/command".to_owned(),
14908                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
14909                                }),
14910                                ..lsp::CompletionItem::default()
14911                            },
14912                            lsp::CompletionItem {
14913                                label: "unregistered_command".to_owned(),
14914                                text_edit: gen_text_edit(&params, ""),
14915                                command: Some(lsp::Command {
14916                                    title: "????????????".to_owned(),
14917                                    command: "????????????".to_owned(),
14918                                    arguments: Some(vec![serde_json::Value::Null]),
14919                                }),
14920                                ..lsp::CompletionItem::default()
14921                            },
14922                        ])))
14923                    },
14924                );
14925                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
14926                    let command_calls = closure_command_calls.clone();
14927                    move |params, _| {
14928                        assert_eq!(params.command, registered_command);
14929                        let command_calls = command_calls.clone();
14930                        async move {
14931                            command_calls.fetch_add(1, atomic::Ordering::Release);
14932                            Ok(Some(json!(null)))
14933                        }
14934                    }
14935                });
14936            })),
14937            ..FakeLspAdapter::default()
14938        },
14939    );
14940    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14941    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14942    let editor = workspace
14943        .update(cx, |workspace, window, cx| {
14944            workspace.open_abs_path(
14945                PathBuf::from(path!("/a/main.rs")),
14946                OpenOptions::default(),
14947                window,
14948                cx,
14949            )
14950        })
14951        .unwrap()
14952        .await
14953        .unwrap()
14954        .downcast::<Editor>()
14955        .unwrap();
14956    let _fake_server = fake_servers.next().await.unwrap();
14957
14958    editor.update_in(cx, |editor, window, cx| {
14959        cx.focus_self(window);
14960        editor.move_to_end(&MoveToEnd, window, cx);
14961        editor.handle_input(".", window, cx);
14962    });
14963    cx.run_until_parked();
14964    editor.update(cx, |editor, _| {
14965        assert!(editor.context_menu_visible());
14966        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14967        {
14968            let completion_labels = menu
14969                .completions
14970                .borrow()
14971                .iter()
14972                .map(|c| c.label.text.clone())
14973                .collect::<Vec<_>>();
14974            assert_eq!(
14975                completion_labels,
14976                &["registered_command", "unregistered_command",],
14977            );
14978        } else {
14979            panic!("expected completion menu to be open");
14980        }
14981    });
14982
14983    editor
14984        .update_in(cx, |editor, window, cx| {
14985            editor
14986                .confirm_completion(&ConfirmCompletion::default(), window, cx)
14987                .unwrap()
14988        })
14989        .await
14990        .unwrap();
14991    cx.run_until_parked();
14992    assert_eq!(
14993        command_calls.load(atomic::Ordering::Acquire),
14994        1,
14995        "For completion with a registered command, Zed should send a command execution request",
14996    );
14997
14998    editor.update_in(cx, |editor, window, cx| {
14999        cx.focus_self(window);
15000        editor.handle_input(".", window, cx);
15001    });
15002    cx.run_until_parked();
15003    editor.update(cx, |editor, _| {
15004        assert!(editor.context_menu_visible());
15005        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15006        {
15007            let completion_labels = menu
15008                .completions
15009                .borrow()
15010                .iter()
15011                .map(|c| c.label.text.clone())
15012                .collect::<Vec<_>>();
15013            assert_eq!(
15014                completion_labels,
15015                &["registered_command", "unregistered_command",],
15016            );
15017        } else {
15018            panic!("expected completion menu to be open");
15019        }
15020    });
15021    editor
15022        .update_in(cx, |editor, window, cx| {
15023            editor.context_menu_next(&Default::default(), window, cx);
15024            editor
15025                .confirm_completion(&ConfirmCompletion::default(), window, cx)
15026                .unwrap()
15027        })
15028        .await
15029        .unwrap();
15030    cx.run_until_parked();
15031    assert_eq!(
15032        command_calls.load(atomic::Ordering::Acquire),
15033        1,
15034        "For completion with an unregistered command, Zed should not send a command execution request",
15035    );
15036}
15037
15038#[gpui::test]
15039async fn test_completion_reuse(cx: &mut TestAppContext) {
15040    init_test(cx, |_| {});
15041
15042    let mut cx = EditorLspTestContext::new_rust(
15043        lsp::ServerCapabilities {
15044            completion_provider: Some(lsp::CompletionOptions {
15045                trigger_characters: Some(vec![".".to_string()]),
15046                ..Default::default()
15047            }),
15048            ..Default::default()
15049        },
15050        cx,
15051    )
15052    .await;
15053
15054    let counter = Arc::new(AtomicUsize::new(0));
15055    cx.set_state("objˇ");
15056    cx.simulate_keystroke(".");
15057
15058    // Initial completion request returns complete results
15059    let is_incomplete = false;
15060    handle_completion_request(
15061        "obj.|<>",
15062        vec!["a", "ab", "abc"],
15063        is_incomplete,
15064        counter.clone(),
15065        &mut cx,
15066    )
15067    .await;
15068    cx.run_until_parked();
15069    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15070    cx.assert_editor_state("obj.ˇ");
15071    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15072
15073    // Type "a" - filters existing completions
15074    cx.simulate_keystroke("a");
15075    cx.run_until_parked();
15076    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15077    cx.assert_editor_state("obj.aˇ");
15078    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15079
15080    // Type "b" - filters existing completions
15081    cx.simulate_keystroke("b");
15082    cx.run_until_parked();
15083    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15084    cx.assert_editor_state("obj.abˇ");
15085    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15086
15087    // Type "c" - filters existing completions
15088    cx.simulate_keystroke("c");
15089    cx.run_until_parked();
15090    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15091    cx.assert_editor_state("obj.abcˇ");
15092    check_displayed_completions(vec!["abc"], &mut cx);
15093
15094    // Backspace to delete "c" - filters existing completions
15095    cx.update_editor(|editor, window, cx| {
15096        editor.backspace(&Backspace, window, cx);
15097    });
15098    cx.run_until_parked();
15099    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15100    cx.assert_editor_state("obj.abˇ");
15101    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15102
15103    // Moving cursor to the left dismisses menu.
15104    cx.update_editor(|editor, window, cx| {
15105        editor.move_left(&MoveLeft, window, cx);
15106    });
15107    cx.run_until_parked();
15108    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15109    cx.assert_editor_state("obj.aˇb");
15110    cx.update_editor(|editor, _, _| {
15111        assert_eq!(editor.context_menu_visible(), false);
15112    });
15113
15114    // Type "b" - new request
15115    cx.simulate_keystroke("b");
15116    let is_incomplete = false;
15117    handle_completion_request(
15118        "obj.<ab|>a",
15119        vec!["ab", "abc"],
15120        is_incomplete,
15121        counter.clone(),
15122        &mut cx,
15123    )
15124    .await;
15125    cx.run_until_parked();
15126    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15127    cx.assert_editor_state("obj.abˇb");
15128    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15129
15130    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15131    cx.update_editor(|editor, window, cx| {
15132        editor.backspace(&Backspace, window, cx);
15133    });
15134    let is_incomplete = false;
15135    handle_completion_request(
15136        "obj.<a|>b",
15137        vec!["a", "ab", "abc"],
15138        is_incomplete,
15139        counter.clone(),
15140        &mut cx,
15141    )
15142    .await;
15143    cx.run_until_parked();
15144    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15145    cx.assert_editor_state("obj.aˇb");
15146    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15147
15148    // Backspace to delete "a" - dismisses menu.
15149    cx.update_editor(|editor, window, cx| {
15150        editor.backspace(&Backspace, window, cx);
15151    });
15152    cx.run_until_parked();
15153    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15154    cx.assert_editor_state("obj.ˇb");
15155    cx.update_editor(|editor, _, _| {
15156        assert_eq!(editor.context_menu_visible(), false);
15157    });
15158}
15159
15160#[gpui::test]
15161async fn test_word_completion(cx: &mut TestAppContext) {
15162    let lsp_fetch_timeout_ms = 10;
15163    init_test(cx, |language_settings| {
15164        language_settings.defaults.completions = Some(CompletionSettingsContent {
15165            words_min_length: Some(0),
15166            lsp_fetch_timeout_ms: Some(10),
15167            lsp_insert_mode: Some(LspInsertMode::Insert),
15168            ..Default::default()
15169        });
15170    });
15171
15172    let mut cx = EditorLspTestContext::new_rust(
15173        lsp::ServerCapabilities {
15174            completion_provider: Some(lsp::CompletionOptions {
15175                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15176                ..lsp::CompletionOptions::default()
15177            }),
15178            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15179            ..lsp::ServerCapabilities::default()
15180        },
15181        cx,
15182    )
15183    .await;
15184
15185    let throttle_completions = Arc::new(AtomicBool::new(false));
15186
15187    let lsp_throttle_completions = throttle_completions.clone();
15188    let _completion_requests_handler =
15189        cx.lsp
15190            .server
15191            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15192                let lsp_throttle_completions = lsp_throttle_completions.clone();
15193                let cx = cx.clone();
15194                async move {
15195                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15196                        cx.background_executor()
15197                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15198                            .await;
15199                    }
15200                    Ok(Some(lsp::CompletionResponse::Array(vec![
15201                        lsp::CompletionItem {
15202                            label: "first".into(),
15203                            ..lsp::CompletionItem::default()
15204                        },
15205                        lsp::CompletionItem {
15206                            label: "last".into(),
15207                            ..lsp::CompletionItem::default()
15208                        },
15209                    ])))
15210                }
15211            });
15212
15213    cx.set_state(indoc! {"
15214        oneˇ
15215        two
15216        three
15217    "});
15218    cx.simulate_keystroke(".");
15219    cx.executor().run_until_parked();
15220    cx.condition(|editor, _| editor.context_menu_visible())
15221        .await;
15222    cx.update_editor(|editor, window, cx| {
15223        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15224        {
15225            assert_eq!(
15226                completion_menu_entries(menu),
15227                &["first", "last"],
15228                "When LSP server is fast to reply, no fallback word completions are used"
15229            );
15230        } else {
15231            panic!("expected completion menu to be open");
15232        }
15233        editor.cancel(&Cancel, window, cx);
15234    });
15235    cx.executor().run_until_parked();
15236    cx.condition(|editor, _| !editor.context_menu_visible())
15237        .await;
15238
15239    throttle_completions.store(true, atomic::Ordering::Release);
15240    cx.simulate_keystroke(".");
15241    cx.executor()
15242        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15243    cx.executor().run_until_parked();
15244    cx.condition(|editor, _| editor.context_menu_visible())
15245        .await;
15246    cx.update_editor(|editor, _, _| {
15247        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15248        {
15249            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15250                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15251        } else {
15252            panic!("expected completion menu to be open");
15253        }
15254    });
15255}
15256
15257#[gpui::test]
15258async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15259    init_test(cx, |language_settings| {
15260        language_settings.defaults.completions = Some(CompletionSettingsContent {
15261            words: Some(WordsCompletionMode::Enabled),
15262            words_min_length: Some(0),
15263            lsp_insert_mode: Some(LspInsertMode::Insert),
15264            ..Default::default()
15265        });
15266    });
15267
15268    let mut cx = EditorLspTestContext::new_rust(
15269        lsp::ServerCapabilities {
15270            completion_provider: Some(lsp::CompletionOptions {
15271                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15272                ..lsp::CompletionOptions::default()
15273            }),
15274            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15275            ..lsp::ServerCapabilities::default()
15276        },
15277        cx,
15278    )
15279    .await;
15280
15281    let _completion_requests_handler =
15282        cx.lsp
15283            .server
15284            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15285                Ok(Some(lsp::CompletionResponse::Array(vec![
15286                    lsp::CompletionItem {
15287                        label: "first".into(),
15288                        ..lsp::CompletionItem::default()
15289                    },
15290                    lsp::CompletionItem {
15291                        label: "last".into(),
15292                        ..lsp::CompletionItem::default()
15293                    },
15294                ])))
15295            });
15296
15297    cx.set_state(indoc! {"ˇ
15298        first
15299        last
15300        second
15301    "});
15302    cx.simulate_keystroke(".");
15303    cx.executor().run_until_parked();
15304    cx.condition(|editor, _| editor.context_menu_visible())
15305        .await;
15306    cx.update_editor(|editor, _, _| {
15307        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15308        {
15309            assert_eq!(
15310                completion_menu_entries(menu),
15311                &["first", "last", "second"],
15312                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15313            );
15314        } else {
15315            panic!("expected completion menu to be open");
15316        }
15317    });
15318}
15319
15320#[gpui::test]
15321async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15322    init_test(cx, |language_settings| {
15323        language_settings.defaults.completions = Some(CompletionSettingsContent {
15324            words: Some(WordsCompletionMode::Disabled),
15325            words_min_length: Some(0),
15326            lsp_insert_mode: Some(LspInsertMode::Insert),
15327            ..Default::default()
15328        });
15329    });
15330
15331    let mut cx = EditorLspTestContext::new_rust(
15332        lsp::ServerCapabilities {
15333            completion_provider: Some(lsp::CompletionOptions {
15334                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15335                ..lsp::CompletionOptions::default()
15336            }),
15337            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15338            ..lsp::ServerCapabilities::default()
15339        },
15340        cx,
15341    )
15342    .await;
15343
15344    let _completion_requests_handler =
15345        cx.lsp
15346            .server
15347            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15348                panic!("LSP completions should not be queried when dealing with word completions")
15349            });
15350
15351    cx.set_state(indoc! {"ˇ
15352        first
15353        last
15354        second
15355    "});
15356    cx.update_editor(|editor, window, cx| {
15357        editor.show_word_completions(&ShowWordCompletions, window, cx);
15358    });
15359    cx.executor().run_until_parked();
15360    cx.condition(|editor, _| editor.context_menu_visible())
15361        .await;
15362    cx.update_editor(|editor, _, _| {
15363        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15364        {
15365            assert_eq!(
15366                completion_menu_entries(menu),
15367                &["first", "last", "second"],
15368                "`ShowWordCompletions` action should show word completions"
15369            );
15370        } else {
15371            panic!("expected completion menu to be open");
15372        }
15373    });
15374
15375    cx.simulate_keystroke("l");
15376    cx.executor().run_until_parked();
15377    cx.condition(|editor, _| editor.context_menu_visible())
15378        .await;
15379    cx.update_editor(|editor, _, _| {
15380        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15381        {
15382            assert_eq!(
15383                completion_menu_entries(menu),
15384                &["last"],
15385                "After showing word completions, further editing should filter them and not query the LSP"
15386            );
15387        } else {
15388            panic!("expected completion menu to be open");
15389        }
15390    });
15391}
15392
15393#[gpui::test]
15394async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15395    init_test(cx, |language_settings| {
15396        language_settings.defaults.completions = Some(CompletionSettingsContent {
15397            words_min_length: Some(0),
15398            lsp: Some(false),
15399            lsp_insert_mode: Some(LspInsertMode::Insert),
15400            ..Default::default()
15401        });
15402    });
15403
15404    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15405
15406    cx.set_state(indoc! {"ˇ
15407        0_usize
15408        let
15409        33
15410        4.5f32
15411    "});
15412    cx.update_editor(|editor, window, cx| {
15413        editor.show_completions(&ShowCompletions, window, cx);
15414    });
15415    cx.executor().run_until_parked();
15416    cx.condition(|editor, _| editor.context_menu_visible())
15417        .await;
15418    cx.update_editor(|editor, window, cx| {
15419        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15420        {
15421            assert_eq!(
15422                completion_menu_entries(menu),
15423                &["let"],
15424                "With no digits in the completion query, no digits should be in the word completions"
15425            );
15426        } else {
15427            panic!("expected completion menu to be open");
15428        }
15429        editor.cancel(&Cancel, window, cx);
15430    });
15431
15432    cx.set_state(indoc! {"15433        0_usize
15434        let
15435        3
15436        33.35f32
15437    "});
15438    cx.update_editor(|editor, window, cx| {
15439        editor.show_completions(&ShowCompletions, window, cx);
15440    });
15441    cx.executor().run_until_parked();
15442    cx.condition(|editor, _| editor.context_menu_visible())
15443        .await;
15444    cx.update_editor(|editor, _, _| {
15445        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15446        {
15447            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15448                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15449        } else {
15450            panic!("expected completion menu to be open");
15451        }
15452    });
15453}
15454
15455#[gpui::test]
15456async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15457    init_test(cx, |language_settings| {
15458        language_settings.defaults.completions = Some(CompletionSettingsContent {
15459            words: Some(WordsCompletionMode::Enabled),
15460            words_min_length: Some(3),
15461            lsp_insert_mode: Some(LspInsertMode::Insert),
15462            ..Default::default()
15463        });
15464    });
15465
15466    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15467    cx.set_state(indoc! {"ˇ
15468        wow
15469        wowen
15470        wowser
15471    "});
15472    cx.simulate_keystroke("w");
15473    cx.executor().run_until_parked();
15474    cx.update_editor(|editor, _, _| {
15475        if editor.context_menu.borrow_mut().is_some() {
15476            panic!(
15477                "expected completion menu to be hidden, as words completion threshold is not met"
15478            );
15479        }
15480    });
15481
15482    cx.update_editor(|editor, window, cx| {
15483        editor.show_word_completions(&ShowWordCompletions, window, cx);
15484    });
15485    cx.executor().run_until_parked();
15486    cx.update_editor(|editor, window, cx| {
15487        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15488        {
15489            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");
15490        } else {
15491            panic!("expected completion menu to be open after the word completions are called with an action");
15492        }
15493
15494        editor.cancel(&Cancel, window, cx);
15495    });
15496    cx.update_editor(|editor, _, _| {
15497        if editor.context_menu.borrow_mut().is_some() {
15498            panic!("expected completion menu to be hidden after canceling");
15499        }
15500    });
15501
15502    cx.simulate_keystroke("o");
15503    cx.executor().run_until_parked();
15504    cx.update_editor(|editor, _, _| {
15505        if editor.context_menu.borrow_mut().is_some() {
15506            panic!(
15507                "expected completion menu to be hidden, as words completion threshold is not met still"
15508            );
15509        }
15510    });
15511
15512    cx.simulate_keystroke("w");
15513    cx.executor().run_until_parked();
15514    cx.update_editor(|editor, _, _| {
15515        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15516        {
15517            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15518        } else {
15519            panic!("expected completion menu to be open after the word completions threshold is met");
15520        }
15521    });
15522}
15523
15524#[gpui::test]
15525async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15526    init_test(cx, |language_settings| {
15527        language_settings.defaults.completions = Some(CompletionSettingsContent {
15528            words: Some(WordsCompletionMode::Enabled),
15529            words_min_length: Some(0),
15530            lsp_insert_mode: Some(LspInsertMode::Insert),
15531            ..Default::default()
15532        });
15533    });
15534
15535    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15536    cx.update_editor(|editor, _, _| {
15537        editor.disable_word_completions();
15538    });
15539    cx.set_state(indoc! {"ˇ
15540        wow
15541        wowen
15542        wowser
15543    "});
15544    cx.simulate_keystroke("w");
15545    cx.executor().run_until_parked();
15546    cx.update_editor(|editor, _, _| {
15547        if editor.context_menu.borrow_mut().is_some() {
15548            panic!(
15549                "expected completion menu to be hidden, as words completion are disabled for this editor"
15550            );
15551        }
15552    });
15553
15554    cx.update_editor(|editor, window, cx| {
15555        editor.show_word_completions(&ShowWordCompletions, window, cx);
15556    });
15557    cx.executor().run_until_parked();
15558    cx.update_editor(|editor, _, _| {
15559        if editor.context_menu.borrow_mut().is_some() {
15560            panic!(
15561                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15562            );
15563        }
15564    });
15565}
15566
15567#[gpui::test]
15568async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15569    init_test(cx, |language_settings| {
15570        language_settings.defaults.completions = Some(CompletionSettingsContent {
15571            words: Some(WordsCompletionMode::Disabled),
15572            words_min_length: Some(0),
15573            lsp_insert_mode: Some(LspInsertMode::Insert),
15574            ..Default::default()
15575        });
15576    });
15577
15578    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15579    cx.update_editor(|editor, _, _| {
15580        editor.set_completion_provider(None);
15581    });
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!("expected completion menu to be hidden, as disabled in settings");
15592        }
15593    });
15594}
15595
15596fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15597    let position = || lsp::Position {
15598        line: params.text_document_position.position.line,
15599        character: params.text_document_position.position.character,
15600    };
15601    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15602        range: lsp::Range {
15603            start: position(),
15604            end: position(),
15605        },
15606        new_text: text.to_string(),
15607    }))
15608}
15609
15610#[gpui::test]
15611async fn test_multiline_completion(cx: &mut TestAppContext) {
15612    init_test(cx, |_| {});
15613
15614    let fs = FakeFs::new(cx.executor());
15615    fs.insert_tree(
15616        path!("/a"),
15617        json!({
15618            "main.ts": "a",
15619        }),
15620    )
15621    .await;
15622
15623    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15624    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15625    let typescript_language = Arc::new(Language::new(
15626        LanguageConfig {
15627            name: "TypeScript".into(),
15628            matcher: LanguageMatcher {
15629                path_suffixes: vec!["ts".to_string()],
15630                ..LanguageMatcher::default()
15631            },
15632            line_comments: vec!["// ".into()],
15633            ..LanguageConfig::default()
15634        },
15635        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15636    ));
15637    language_registry.add(typescript_language.clone());
15638    let mut fake_servers = language_registry.register_fake_lsp(
15639        "TypeScript",
15640        FakeLspAdapter {
15641            capabilities: lsp::ServerCapabilities {
15642                completion_provider: Some(lsp::CompletionOptions {
15643                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15644                    ..lsp::CompletionOptions::default()
15645                }),
15646                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15647                ..lsp::ServerCapabilities::default()
15648            },
15649            // Emulate vtsls label generation
15650            label_for_completion: Some(Box::new(|item, _| {
15651                let text = if let Some(description) = item
15652                    .label_details
15653                    .as_ref()
15654                    .and_then(|label_details| label_details.description.as_ref())
15655                {
15656                    format!("{} {}", item.label, description)
15657                } else if let Some(detail) = &item.detail {
15658                    format!("{} {}", item.label, detail)
15659                } else {
15660                    item.label.clone()
15661                };
15662                Some(language::CodeLabel::plain(text, None))
15663            })),
15664            ..FakeLspAdapter::default()
15665        },
15666    );
15667    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15668    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15669    let worktree_id = workspace
15670        .update(cx, |workspace, _window, cx| {
15671            workspace.project().update(cx, |project, cx| {
15672                project.worktrees(cx).next().unwrap().read(cx).id()
15673            })
15674        })
15675        .unwrap();
15676    let _buffer = project
15677        .update(cx, |project, cx| {
15678            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15679        })
15680        .await
15681        .unwrap();
15682    let editor = workspace
15683        .update(cx, |workspace, window, cx| {
15684            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15685        })
15686        .unwrap()
15687        .await
15688        .unwrap()
15689        .downcast::<Editor>()
15690        .unwrap();
15691    let fake_server = fake_servers.next().await.unwrap();
15692
15693    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15694    let multiline_label_2 = "a\nb\nc\n";
15695    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15696    let multiline_description = "d\ne\nf\n";
15697    let multiline_detail_2 = "g\nh\ni\n";
15698
15699    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15700        move |params, _| async move {
15701            Ok(Some(lsp::CompletionResponse::Array(vec![
15702                lsp::CompletionItem {
15703                    label: multiline_label.to_string(),
15704                    text_edit: gen_text_edit(&params, "new_text_1"),
15705                    ..lsp::CompletionItem::default()
15706                },
15707                lsp::CompletionItem {
15708                    label: "single line label 1".to_string(),
15709                    detail: Some(multiline_detail.to_string()),
15710                    text_edit: gen_text_edit(&params, "new_text_2"),
15711                    ..lsp::CompletionItem::default()
15712                },
15713                lsp::CompletionItem {
15714                    label: "single line label 2".to_string(),
15715                    label_details: Some(lsp::CompletionItemLabelDetails {
15716                        description: Some(multiline_description.to_string()),
15717                        detail: None,
15718                    }),
15719                    text_edit: gen_text_edit(&params, "new_text_2"),
15720                    ..lsp::CompletionItem::default()
15721                },
15722                lsp::CompletionItem {
15723                    label: multiline_label_2.to_string(),
15724                    detail: Some(multiline_detail_2.to_string()),
15725                    text_edit: gen_text_edit(&params, "new_text_3"),
15726                    ..lsp::CompletionItem::default()
15727                },
15728                lsp::CompletionItem {
15729                    label: "Label with many     spaces and \t but without newlines".to_string(),
15730                    detail: Some(
15731                        "Details with many     spaces and \t but without newlines".to_string(),
15732                    ),
15733                    text_edit: gen_text_edit(&params, "new_text_4"),
15734                    ..lsp::CompletionItem::default()
15735                },
15736            ])))
15737        },
15738    );
15739
15740    editor.update_in(cx, |editor, window, cx| {
15741        cx.focus_self(window);
15742        editor.move_to_end(&MoveToEnd, window, cx);
15743        editor.handle_input(".", window, cx);
15744    });
15745    cx.run_until_parked();
15746    completion_handle.next().await.unwrap();
15747
15748    editor.update(cx, |editor, _| {
15749        assert!(editor.context_menu_visible());
15750        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15751        {
15752            let completion_labels = menu
15753                .completions
15754                .borrow()
15755                .iter()
15756                .map(|c| c.label.text.clone())
15757                .collect::<Vec<_>>();
15758            assert_eq!(
15759                completion_labels,
15760                &[
15761                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15762                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15763                    "single line label 2 d e f ",
15764                    "a b c g h i ",
15765                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15766                ],
15767                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15768            );
15769
15770            for completion in menu
15771                .completions
15772                .borrow()
15773                .iter() {
15774                    assert_eq!(
15775                        completion.label.filter_range,
15776                        0..completion.label.text.len(),
15777                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15778                    );
15779                }
15780        } else {
15781            panic!("expected completion menu to be open");
15782        }
15783    });
15784}
15785
15786#[gpui::test]
15787async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15788    init_test(cx, |_| {});
15789    let mut cx = EditorLspTestContext::new_rust(
15790        lsp::ServerCapabilities {
15791            completion_provider: Some(lsp::CompletionOptions {
15792                trigger_characters: Some(vec![".".to_string()]),
15793                ..Default::default()
15794            }),
15795            ..Default::default()
15796        },
15797        cx,
15798    )
15799    .await;
15800    cx.lsp
15801        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15802            Ok(Some(lsp::CompletionResponse::Array(vec![
15803                lsp::CompletionItem {
15804                    label: "first".into(),
15805                    ..Default::default()
15806                },
15807                lsp::CompletionItem {
15808                    label: "last".into(),
15809                    ..Default::default()
15810                },
15811            ])))
15812        });
15813    cx.set_state("variableˇ");
15814    cx.simulate_keystroke(".");
15815    cx.executor().run_until_parked();
15816
15817    cx.update_editor(|editor, _, _| {
15818        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15819        {
15820            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15821        } else {
15822            panic!("expected completion menu to be open");
15823        }
15824    });
15825
15826    cx.update_editor(|editor, window, cx| {
15827        editor.move_page_down(&MovePageDown::default(), window, cx);
15828        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15829        {
15830            assert!(
15831                menu.selected_item == 1,
15832                "expected PageDown to select the last item from the context menu"
15833            );
15834        } else {
15835            panic!("expected completion menu to stay open after PageDown");
15836        }
15837    });
15838
15839    cx.update_editor(|editor, window, cx| {
15840        editor.move_page_up(&MovePageUp::default(), window, cx);
15841        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15842        {
15843            assert!(
15844                menu.selected_item == 0,
15845                "expected PageUp to select the first item from the context menu"
15846            );
15847        } else {
15848            panic!("expected completion menu to stay open after PageUp");
15849        }
15850    });
15851}
15852
15853#[gpui::test]
15854async fn test_as_is_completions(cx: &mut TestAppContext) {
15855    init_test(cx, |_| {});
15856    let mut cx = EditorLspTestContext::new_rust(
15857        lsp::ServerCapabilities {
15858            completion_provider: Some(lsp::CompletionOptions {
15859                ..Default::default()
15860            }),
15861            ..Default::default()
15862        },
15863        cx,
15864    )
15865    .await;
15866    cx.lsp
15867        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15868            Ok(Some(lsp::CompletionResponse::Array(vec![
15869                lsp::CompletionItem {
15870                    label: "unsafe".into(),
15871                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15872                        range: lsp::Range {
15873                            start: lsp::Position {
15874                                line: 1,
15875                                character: 2,
15876                            },
15877                            end: lsp::Position {
15878                                line: 1,
15879                                character: 3,
15880                            },
15881                        },
15882                        new_text: "unsafe".to_string(),
15883                    })),
15884                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15885                    ..Default::default()
15886                },
15887            ])))
15888        });
15889    cx.set_state("fn a() {}\n");
15890    cx.executor().run_until_parked();
15891    cx.update_editor(|editor, window, cx| {
15892        editor.trigger_completion_on_input("n", true, window, cx)
15893    });
15894    cx.executor().run_until_parked();
15895
15896    cx.update_editor(|editor, window, cx| {
15897        editor.confirm_completion(&Default::default(), window, cx)
15898    });
15899    cx.executor().run_until_parked();
15900    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15901}
15902
15903#[gpui::test]
15904async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15905    init_test(cx, |_| {});
15906    let language =
15907        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15908    let mut cx = EditorLspTestContext::new(
15909        language,
15910        lsp::ServerCapabilities {
15911            completion_provider: Some(lsp::CompletionOptions {
15912                ..lsp::CompletionOptions::default()
15913            }),
15914            ..lsp::ServerCapabilities::default()
15915        },
15916        cx,
15917    )
15918    .await;
15919
15920    cx.set_state(
15921        "#ifndef BAR_H
15922#define BAR_H
15923
15924#include <stdbool.h>
15925
15926int fn_branch(bool do_branch1, bool do_branch2);
15927
15928#endif // BAR_H
15929ˇ",
15930    );
15931    cx.executor().run_until_parked();
15932    cx.update_editor(|editor, window, cx| {
15933        editor.handle_input("#", window, cx);
15934    });
15935    cx.executor().run_until_parked();
15936    cx.update_editor(|editor, window, cx| {
15937        editor.handle_input("i", window, cx);
15938    });
15939    cx.executor().run_until_parked();
15940    cx.update_editor(|editor, window, cx| {
15941        editor.handle_input("n", window, cx);
15942    });
15943    cx.executor().run_until_parked();
15944    cx.assert_editor_state(
15945        "#ifndef BAR_H
15946#define BAR_H
15947
15948#include <stdbool.h>
15949
15950int fn_branch(bool do_branch1, bool do_branch2);
15951
15952#endif // BAR_H
15953#inˇ",
15954    );
15955
15956    cx.lsp
15957        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15958            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15959                is_incomplete: false,
15960                item_defaults: None,
15961                items: vec![lsp::CompletionItem {
15962                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15963                    label_details: Some(lsp::CompletionItemLabelDetails {
15964                        detail: Some("header".to_string()),
15965                        description: None,
15966                    }),
15967                    label: " include".to_string(),
15968                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15969                        range: lsp::Range {
15970                            start: lsp::Position {
15971                                line: 8,
15972                                character: 1,
15973                            },
15974                            end: lsp::Position {
15975                                line: 8,
15976                                character: 1,
15977                            },
15978                        },
15979                        new_text: "include \"$0\"".to_string(),
15980                    })),
15981                    sort_text: Some("40b67681include".to_string()),
15982                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15983                    filter_text: Some("include".to_string()),
15984                    insert_text: Some("include \"$0\"".to_string()),
15985                    ..lsp::CompletionItem::default()
15986                }],
15987            })))
15988        });
15989    cx.update_editor(|editor, window, cx| {
15990        editor.show_completions(&ShowCompletions, window, cx);
15991    });
15992    cx.executor().run_until_parked();
15993    cx.update_editor(|editor, window, cx| {
15994        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15995    });
15996    cx.executor().run_until_parked();
15997    cx.assert_editor_state(
15998        "#ifndef BAR_H
15999#define BAR_H
16000
16001#include <stdbool.h>
16002
16003int fn_branch(bool do_branch1, bool do_branch2);
16004
16005#endif // BAR_H
16006#include \"ˇ\"",
16007    );
16008
16009    cx.lsp
16010        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
16011            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16012                is_incomplete: true,
16013                item_defaults: None,
16014                items: vec![lsp::CompletionItem {
16015                    kind: Some(lsp::CompletionItemKind::FILE),
16016                    label: "AGL/".to_string(),
16017                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16018                        range: lsp::Range {
16019                            start: lsp::Position {
16020                                line: 8,
16021                                character: 10,
16022                            },
16023                            end: lsp::Position {
16024                                line: 8,
16025                                character: 11,
16026                            },
16027                        },
16028                        new_text: "AGL/".to_string(),
16029                    })),
16030                    sort_text: Some("40b67681AGL/".to_string()),
16031                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
16032                    filter_text: Some("AGL/".to_string()),
16033                    insert_text: Some("AGL/".to_string()),
16034                    ..lsp::CompletionItem::default()
16035                }],
16036            })))
16037        });
16038    cx.update_editor(|editor, window, cx| {
16039        editor.show_completions(&ShowCompletions, window, cx);
16040    });
16041    cx.executor().run_until_parked();
16042    cx.update_editor(|editor, window, cx| {
16043        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
16044    });
16045    cx.executor().run_until_parked();
16046    cx.assert_editor_state(
16047        r##"#ifndef BAR_H
16048#define BAR_H
16049
16050#include <stdbool.h>
16051
16052int fn_branch(bool do_branch1, bool do_branch2);
16053
16054#endif // BAR_H
16055#include "AGL/ˇ"##,
16056    );
16057
16058    cx.update_editor(|editor, window, cx| {
16059        editor.handle_input("\"", window, cx);
16060    });
16061    cx.executor().run_until_parked();
16062    cx.assert_editor_state(
16063        r##"#ifndef BAR_H
16064#define BAR_H
16065
16066#include <stdbool.h>
16067
16068int fn_branch(bool do_branch1, bool do_branch2);
16069
16070#endif // BAR_H
16071#include "AGL/"ˇ"##,
16072    );
16073}
16074
16075#[gpui::test]
16076async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
16077    init_test(cx, |_| {});
16078
16079    let mut cx = EditorLspTestContext::new_rust(
16080        lsp::ServerCapabilities {
16081            completion_provider: Some(lsp::CompletionOptions {
16082                trigger_characters: Some(vec![".".to_string()]),
16083                resolve_provider: Some(true),
16084                ..Default::default()
16085            }),
16086            ..Default::default()
16087        },
16088        cx,
16089    )
16090    .await;
16091
16092    cx.set_state("fn main() { let a = 2ˇ; }");
16093    cx.simulate_keystroke(".");
16094    let completion_item = lsp::CompletionItem {
16095        label: "Some".into(),
16096        kind: Some(lsp::CompletionItemKind::SNIPPET),
16097        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
16098        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
16099            kind: lsp::MarkupKind::Markdown,
16100            value: "```rust\nSome(2)\n```".to_string(),
16101        })),
16102        deprecated: Some(false),
16103        sort_text: Some("Some".to_string()),
16104        filter_text: Some("Some".to_string()),
16105        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16106        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16107            range: lsp::Range {
16108                start: lsp::Position {
16109                    line: 0,
16110                    character: 22,
16111                },
16112                end: lsp::Position {
16113                    line: 0,
16114                    character: 22,
16115                },
16116            },
16117            new_text: "Some(2)".to_string(),
16118        })),
16119        additional_text_edits: Some(vec![lsp::TextEdit {
16120            range: lsp::Range {
16121                start: lsp::Position {
16122                    line: 0,
16123                    character: 20,
16124                },
16125                end: lsp::Position {
16126                    line: 0,
16127                    character: 22,
16128                },
16129            },
16130            new_text: "".to_string(),
16131        }]),
16132        ..Default::default()
16133    };
16134
16135    let closure_completion_item = completion_item.clone();
16136    let counter = Arc::new(AtomicUsize::new(0));
16137    let counter_clone = counter.clone();
16138    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16139        let task_completion_item = closure_completion_item.clone();
16140        counter_clone.fetch_add(1, atomic::Ordering::Release);
16141        async move {
16142            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16143                is_incomplete: true,
16144                item_defaults: None,
16145                items: vec![task_completion_item],
16146            })))
16147        }
16148    });
16149
16150    cx.condition(|editor, _| editor.context_menu_visible())
16151        .await;
16152    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16153    assert!(request.next().await.is_some());
16154    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16155
16156    cx.simulate_keystrokes("S o m");
16157    cx.condition(|editor, _| editor.context_menu_visible())
16158        .await;
16159    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16160    assert!(request.next().await.is_some());
16161    assert!(request.next().await.is_some());
16162    assert!(request.next().await.is_some());
16163    request.close();
16164    assert!(request.next().await.is_none());
16165    assert_eq!(
16166        counter.load(atomic::Ordering::Acquire),
16167        4,
16168        "With the completions menu open, only one LSP request should happen per input"
16169    );
16170}
16171
16172#[gpui::test]
16173async fn test_toggle_comment(cx: &mut TestAppContext) {
16174    init_test(cx, |_| {});
16175    let mut cx = EditorTestContext::new(cx).await;
16176    let language = Arc::new(Language::new(
16177        LanguageConfig {
16178            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16179            ..Default::default()
16180        },
16181        Some(tree_sitter_rust::LANGUAGE.into()),
16182    ));
16183    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16184
16185    // If multiple selections intersect a line, the line is only toggled once.
16186    cx.set_state(indoc! {"
16187        fn a() {
16188            «//b();
16189            ˇ»// «c();
16190            //ˇ»  d();
16191        }
16192    "});
16193
16194    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16195
16196    cx.assert_editor_state(indoc! {"
16197        fn a() {
16198            «b();
16199            c();
16200            ˇ» d();
16201        }
16202    "});
16203
16204    // The comment prefix is inserted at the same column for every line in a
16205    // selection.
16206    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16207
16208    cx.assert_editor_state(indoc! {"
16209        fn a() {
16210            // «b();
16211            // c();
16212            ˇ»//  d();
16213        }
16214    "});
16215
16216    // If a selection ends at the beginning of a line, that line is not toggled.
16217    cx.set_selections_state(indoc! {"
16218        fn a() {
16219            // b();
16220            «// c();
16221        ˇ»    //  d();
16222        }
16223    "});
16224
16225    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16226
16227    cx.assert_editor_state(indoc! {"
16228        fn a() {
16229            // b();
16230            «c();
16231        ˇ»    //  d();
16232        }
16233    "});
16234
16235    // If a selection span a single line and is empty, the line is toggled.
16236    cx.set_state(indoc! {"
16237        fn a() {
16238            a();
16239            b();
16240        ˇ
16241        }
16242    "});
16243
16244    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16245
16246    cx.assert_editor_state(indoc! {"
16247        fn a() {
16248            a();
16249            b();
16250        //•ˇ
16251        }
16252    "});
16253
16254    // If a selection span multiple lines, empty lines are not toggled.
16255    cx.set_state(indoc! {"
16256        fn a() {
16257            «a();
16258
16259            c();ˇ»
16260        }
16261    "});
16262
16263    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16264
16265    cx.assert_editor_state(indoc! {"
16266        fn a() {
16267            // «a();
16268
16269            // c();ˇ»
16270        }
16271    "});
16272
16273    // If a selection includes multiple comment prefixes, all lines are uncommented.
16274    cx.set_state(indoc! {"
16275        fn a() {
16276            «// a();
16277            /// b();
16278            //! c();ˇ»
16279        }
16280    "});
16281
16282    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16283
16284    cx.assert_editor_state(indoc! {"
16285        fn a() {
16286            «a();
16287            b();
16288            c();ˇ»
16289        }
16290    "});
16291}
16292
16293#[gpui::test]
16294async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16295    init_test(cx, |_| {});
16296    let mut cx = EditorTestContext::new(cx).await;
16297    let language = Arc::new(Language::new(
16298        LanguageConfig {
16299            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16300            ..Default::default()
16301        },
16302        Some(tree_sitter_rust::LANGUAGE.into()),
16303    ));
16304    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16305
16306    let toggle_comments = &ToggleComments {
16307        advance_downwards: false,
16308        ignore_indent: true,
16309    };
16310
16311    // If multiple selections intersect a line, the line is only toggled once.
16312    cx.set_state(indoc! {"
16313        fn a() {
16314        //    «b();
16315        //    c();
16316        //    ˇ» d();
16317        }
16318    "});
16319
16320    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16321
16322    cx.assert_editor_state(indoc! {"
16323        fn a() {
16324            «b();
16325            c();
16326            ˇ» d();
16327        }
16328    "});
16329
16330    // The comment prefix is inserted at the beginning of each line
16331    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16332
16333    cx.assert_editor_state(indoc! {"
16334        fn a() {
16335        //    «b();
16336        //    c();
16337        //    ˇ» d();
16338        }
16339    "});
16340
16341    // If a selection ends at the beginning of a line, that line is not toggled.
16342    cx.set_selections_state(indoc! {"
16343        fn a() {
16344        //    b();
16345        //    «c();
16346        ˇ»//     d();
16347        }
16348    "});
16349
16350    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16351
16352    cx.assert_editor_state(indoc! {"
16353        fn a() {
16354        //    b();
16355            «c();
16356        ˇ»//     d();
16357        }
16358    "});
16359
16360    // If a selection span a single line and is empty, the line is toggled.
16361    cx.set_state(indoc! {"
16362        fn a() {
16363            a();
16364            b();
16365        ˇ
16366        }
16367    "});
16368
16369    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16370
16371    cx.assert_editor_state(indoc! {"
16372        fn a() {
16373            a();
16374            b();
16375        //ˇ
16376        }
16377    "});
16378
16379    // If a selection span multiple lines, empty lines are not toggled.
16380    cx.set_state(indoc! {"
16381        fn a() {
16382            «a();
16383
16384            c();ˇ»
16385        }
16386    "});
16387
16388    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16389
16390    cx.assert_editor_state(indoc! {"
16391        fn a() {
16392        //    «a();
16393
16394        //    c();ˇ»
16395        }
16396    "});
16397
16398    // If a selection includes multiple comment prefixes, all lines are uncommented.
16399    cx.set_state(indoc! {"
16400        fn a() {
16401        //    «a();
16402        ///    b();
16403        //!    c();ˇ»
16404        }
16405    "});
16406
16407    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16408
16409    cx.assert_editor_state(indoc! {"
16410        fn a() {
16411            «a();
16412            b();
16413            c();ˇ»
16414        }
16415    "});
16416}
16417
16418#[gpui::test]
16419async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16420    init_test(cx, |_| {});
16421
16422    let language = Arc::new(Language::new(
16423        LanguageConfig {
16424            line_comments: vec!["// ".into()],
16425            ..Default::default()
16426        },
16427        Some(tree_sitter_rust::LANGUAGE.into()),
16428    ));
16429
16430    let mut cx = EditorTestContext::new(cx).await;
16431
16432    cx.language_registry().add(language.clone());
16433    cx.update_buffer(|buffer, cx| {
16434        buffer.set_language(Some(language), cx);
16435    });
16436
16437    let toggle_comments = &ToggleComments {
16438        advance_downwards: true,
16439        ignore_indent: false,
16440    };
16441
16442    // Single cursor on one line -> advance
16443    // Cursor moves horizontally 3 characters as well on non-blank line
16444    cx.set_state(indoc!(
16445        "fn a() {
16446             ˇdog();
16447             cat();
16448        }"
16449    ));
16450    cx.update_editor(|editor, window, cx| {
16451        editor.toggle_comments(toggle_comments, window, cx);
16452    });
16453    cx.assert_editor_state(indoc!(
16454        "fn a() {
16455             // dog();
16456             catˇ();
16457        }"
16458    ));
16459
16460    // Single selection on one line -> don't advance
16461    cx.set_state(indoc!(
16462        "fn a() {
16463             «dog()ˇ»;
16464             cat();
16465        }"
16466    ));
16467    cx.update_editor(|editor, window, cx| {
16468        editor.toggle_comments(toggle_comments, window, cx);
16469    });
16470    cx.assert_editor_state(indoc!(
16471        "fn a() {
16472             // «dog()ˇ»;
16473             cat();
16474        }"
16475    ));
16476
16477    // Multiple cursors on one line -> advance
16478    cx.set_state(indoc!(
16479        "fn a() {
16480             ˇdˇog();
16481             cat();
16482        }"
16483    ));
16484    cx.update_editor(|editor, window, cx| {
16485        editor.toggle_comments(toggle_comments, window, cx);
16486    });
16487    cx.assert_editor_state(indoc!(
16488        "fn a() {
16489             // dog();
16490             catˇ(ˇ);
16491        }"
16492    ));
16493
16494    // Multiple cursors on one line, with selection -> don't advance
16495    cx.set_state(indoc!(
16496        "fn a() {
16497             ˇdˇog«()ˇ»;
16498             cat();
16499        }"
16500    ));
16501    cx.update_editor(|editor, window, cx| {
16502        editor.toggle_comments(toggle_comments, window, cx);
16503    });
16504    cx.assert_editor_state(indoc!(
16505        "fn a() {
16506             // ˇdˇog«()ˇ»;
16507             cat();
16508        }"
16509    ));
16510
16511    // Single cursor on one line -> advance
16512    // Cursor moves to column 0 on blank line
16513    cx.set_state(indoc!(
16514        "fn a() {
16515             ˇdog();
16516
16517             cat();
16518        }"
16519    ));
16520    cx.update_editor(|editor, window, cx| {
16521        editor.toggle_comments(toggle_comments, window, cx);
16522    });
16523    cx.assert_editor_state(indoc!(
16524        "fn a() {
16525             // dog();
16526        ˇ
16527             cat();
16528        }"
16529    ));
16530
16531    // Single cursor on one line -> advance
16532    // Cursor starts and ends at column 0
16533    cx.set_state(indoc!(
16534        "fn a() {
16535         ˇ    dog();
16536             cat();
16537        }"
16538    ));
16539    cx.update_editor(|editor, window, cx| {
16540        editor.toggle_comments(toggle_comments, window, cx);
16541    });
16542    cx.assert_editor_state(indoc!(
16543        "fn a() {
16544             // dog();
16545         ˇ    cat();
16546        }"
16547    ));
16548}
16549
16550#[gpui::test]
16551async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16552    init_test(cx, |_| {});
16553
16554    let mut cx = EditorTestContext::new(cx).await;
16555
16556    let html_language = Arc::new(
16557        Language::new(
16558            LanguageConfig {
16559                name: "HTML".into(),
16560                block_comment: Some(BlockCommentConfig {
16561                    start: "<!-- ".into(),
16562                    prefix: "".into(),
16563                    end: " -->".into(),
16564                    tab_size: 0,
16565                }),
16566                ..Default::default()
16567            },
16568            Some(tree_sitter_html::LANGUAGE.into()),
16569        )
16570        .with_injection_query(
16571            r#"
16572            (script_element
16573                (raw_text) @injection.content
16574                (#set! injection.language "javascript"))
16575            "#,
16576        )
16577        .unwrap(),
16578    );
16579
16580    let javascript_language = Arc::new(Language::new(
16581        LanguageConfig {
16582            name: "JavaScript".into(),
16583            line_comments: vec!["// ".into()],
16584            ..Default::default()
16585        },
16586        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16587    ));
16588
16589    cx.language_registry().add(html_language.clone());
16590    cx.language_registry().add(javascript_language);
16591    cx.update_buffer(|buffer, cx| {
16592        buffer.set_language(Some(html_language), cx);
16593    });
16594
16595    // Toggle comments for empty selections
16596    cx.set_state(
16597        &r#"
16598            <p>A</p>ˇ
16599            <p>B</p>ˇ
16600            <p>C</p>ˇ
16601        "#
16602        .unindent(),
16603    );
16604    cx.update_editor(|editor, window, cx| {
16605        editor.toggle_comments(&ToggleComments::default(), window, cx)
16606    });
16607    cx.assert_editor_state(
16608        &r#"
16609            <!-- <p>A</p>ˇ -->
16610            <!-- <p>B</p>ˇ -->
16611            <!-- <p>C</p>ˇ -->
16612        "#
16613        .unindent(),
16614    );
16615    cx.update_editor(|editor, window, cx| {
16616        editor.toggle_comments(&ToggleComments::default(), window, cx)
16617    });
16618    cx.assert_editor_state(
16619        &r#"
16620            <p>A</p>ˇ
16621            <p>B</p>ˇ
16622            <p>C</p>ˇ
16623        "#
16624        .unindent(),
16625    );
16626
16627    // Toggle comments for mixture of empty and non-empty selections, where
16628    // multiple selections occupy a given line.
16629    cx.set_state(
16630        &r#"
16631            <p>A«</p>
16632            <p>ˇ»B</p>ˇ
16633            <p>C«</p>
16634            <p>ˇ»D</p>ˇ
16635        "#
16636        .unindent(),
16637    );
16638
16639    cx.update_editor(|editor, window, cx| {
16640        editor.toggle_comments(&ToggleComments::default(), window, cx)
16641    });
16642    cx.assert_editor_state(
16643        &r#"
16644            <!-- <p>A«</p>
16645            <p>ˇ»B</p>ˇ -->
16646            <!-- <p>C«</p>
16647            <p>ˇ»D</p>ˇ -->
16648        "#
16649        .unindent(),
16650    );
16651    cx.update_editor(|editor, window, cx| {
16652        editor.toggle_comments(&ToggleComments::default(), window, cx)
16653    });
16654    cx.assert_editor_state(
16655        &r#"
16656            <p>A«</p>
16657            <p>ˇ»B</p>ˇ
16658            <p>C«</p>
16659            <p>ˇ»D</p>ˇ
16660        "#
16661        .unindent(),
16662    );
16663
16664    // Toggle comments when different languages are active for different
16665    // selections.
16666    cx.set_state(
16667        &r#"
16668            ˇ<script>
16669                ˇvar x = new Y();
16670            ˇ</script>
16671        "#
16672        .unindent(),
16673    );
16674    cx.executor().run_until_parked();
16675    cx.update_editor(|editor, window, cx| {
16676        editor.toggle_comments(&ToggleComments::default(), window, cx)
16677    });
16678    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16679    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16680    cx.assert_editor_state(
16681        &r#"
16682            <!-- ˇ<script> -->
16683                // ˇvar x = new Y();
16684            <!-- ˇ</script> -->
16685        "#
16686        .unindent(),
16687    );
16688}
16689
16690#[gpui::test]
16691fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16692    init_test(cx, |_| {});
16693
16694    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16695    let multibuffer = cx.new(|cx| {
16696        let mut multibuffer = MultiBuffer::new(ReadWrite);
16697        multibuffer.push_excerpts(
16698            buffer.clone(),
16699            [
16700                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16701                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16702            ],
16703            cx,
16704        );
16705        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16706        multibuffer
16707    });
16708
16709    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16710    editor.update_in(cx, |editor, window, cx| {
16711        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16712        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16713            s.select_ranges([
16714                Point::new(0, 0)..Point::new(0, 0),
16715                Point::new(1, 0)..Point::new(1, 0),
16716            ])
16717        });
16718
16719        editor.handle_input("X", window, cx);
16720        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16721        assert_eq!(
16722            editor.selections.ranges(&editor.display_snapshot(cx)),
16723            [
16724                Point::new(0, 1)..Point::new(0, 1),
16725                Point::new(1, 1)..Point::new(1, 1),
16726            ]
16727        );
16728
16729        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16730        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16731            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16732        });
16733        editor.backspace(&Default::default(), window, cx);
16734        assert_eq!(editor.text(cx), "Xa\nbbb");
16735        assert_eq!(
16736            editor.selections.ranges(&editor.display_snapshot(cx)),
16737            [Point::new(1, 0)..Point::new(1, 0)]
16738        );
16739
16740        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16741            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16742        });
16743        editor.backspace(&Default::default(), window, cx);
16744        assert_eq!(editor.text(cx), "X\nbb");
16745        assert_eq!(
16746            editor.selections.ranges(&editor.display_snapshot(cx)),
16747            [Point::new(0, 1)..Point::new(0, 1)]
16748        );
16749    });
16750}
16751
16752#[gpui::test]
16753fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16754    init_test(cx, |_| {});
16755
16756    let markers = vec![('[', ']').into(), ('(', ')').into()];
16757    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16758        indoc! {"
16759            [aaaa
16760            (bbbb]
16761            cccc)",
16762        },
16763        markers.clone(),
16764    );
16765    let excerpt_ranges = markers.into_iter().map(|marker| {
16766        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16767        ExcerptRange::new(context)
16768    });
16769    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16770    let multibuffer = cx.new(|cx| {
16771        let mut multibuffer = MultiBuffer::new(ReadWrite);
16772        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16773        multibuffer
16774    });
16775
16776    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16777    editor.update_in(cx, |editor, window, cx| {
16778        let (expected_text, selection_ranges) = marked_text_ranges(
16779            indoc! {"
16780                aaaa
16781                bˇbbb
16782                bˇbbˇb
16783                cccc"
16784            },
16785            true,
16786        );
16787        assert_eq!(editor.text(cx), expected_text);
16788        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16789            s.select_ranges(
16790                selection_ranges
16791                    .iter()
16792                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16793            )
16794        });
16795
16796        editor.handle_input("X", window, cx);
16797
16798        let (expected_text, expected_selections) = marked_text_ranges(
16799            indoc! {"
16800                aaaa
16801                bXˇbbXb
16802                bXˇbbXˇb
16803                cccc"
16804            },
16805            false,
16806        );
16807        assert_eq!(editor.text(cx), expected_text);
16808        assert_eq!(
16809            editor.selections.ranges(&editor.display_snapshot(cx)),
16810            expected_selections
16811                .iter()
16812                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16813                .collect::<Vec<_>>()
16814        );
16815
16816        editor.newline(&Newline, window, cx);
16817        let (expected_text, expected_selections) = marked_text_ranges(
16818            indoc! {"
16819                aaaa
16820                bX
16821                ˇbbX
16822                b
16823                bX
16824                ˇbbX
16825                ˇb
16826                cccc"
16827            },
16828            false,
16829        );
16830        assert_eq!(editor.text(cx), expected_text);
16831        assert_eq!(
16832            editor.selections.ranges(&editor.display_snapshot(cx)),
16833            expected_selections
16834                .iter()
16835                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16836                .collect::<Vec<_>>()
16837        );
16838    });
16839}
16840
16841#[gpui::test]
16842fn test_refresh_selections(cx: &mut TestAppContext) {
16843    init_test(cx, |_| {});
16844
16845    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16846    let mut excerpt1_id = None;
16847    let multibuffer = cx.new(|cx| {
16848        let mut multibuffer = MultiBuffer::new(ReadWrite);
16849        excerpt1_id = multibuffer
16850            .push_excerpts(
16851                buffer.clone(),
16852                [
16853                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16854                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16855                ],
16856                cx,
16857            )
16858            .into_iter()
16859            .next();
16860        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16861        multibuffer
16862    });
16863
16864    let editor = cx.add_window(|window, cx| {
16865        let mut editor = build_editor(multibuffer.clone(), window, cx);
16866        let snapshot = editor.snapshot(window, cx);
16867        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16868            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16869        });
16870        editor.begin_selection(
16871            Point::new(2, 1).to_display_point(&snapshot),
16872            true,
16873            1,
16874            window,
16875            cx,
16876        );
16877        assert_eq!(
16878            editor.selections.ranges(&editor.display_snapshot(cx)),
16879            [
16880                Point::new(1, 3)..Point::new(1, 3),
16881                Point::new(2, 1)..Point::new(2, 1),
16882            ]
16883        );
16884        editor
16885    });
16886
16887    // Refreshing selections is a no-op when excerpts haven't changed.
16888    _ = editor.update(cx, |editor, window, cx| {
16889        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16890        assert_eq!(
16891            editor.selections.ranges(&editor.display_snapshot(cx)),
16892            [
16893                Point::new(1, 3)..Point::new(1, 3),
16894                Point::new(2, 1)..Point::new(2, 1),
16895            ]
16896        );
16897    });
16898
16899    multibuffer.update(cx, |multibuffer, cx| {
16900        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16901    });
16902    _ = editor.update(cx, |editor, window, cx| {
16903        // Removing an excerpt causes the first selection to become degenerate.
16904        assert_eq!(
16905            editor.selections.ranges(&editor.display_snapshot(cx)),
16906            [
16907                Point::new(0, 0)..Point::new(0, 0),
16908                Point::new(0, 1)..Point::new(0, 1)
16909            ]
16910        );
16911
16912        // Refreshing selections will relocate the first selection to the original buffer
16913        // location.
16914        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16915        assert_eq!(
16916            editor.selections.ranges(&editor.display_snapshot(cx)),
16917            [
16918                Point::new(0, 1)..Point::new(0, 1),
16919                Point::new(0, 3)..Point::new(0, 3)
16920            ]
16921        );
16922        assert!(editor.selections.pending_anchor().is_some());
16923    });
16924}
16925
16926#[gpui::test]
16927fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16928    init_test(cx, |_| {});
16929
16930    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16931    let mut excerpt1_id = None;
16932    let multibuffer = cx.new(|cx| {
16933        let mut multibuffer = MultiBuffer::new(ReadWrite);
16934        excerpt1_id = multibuffer
16935            .push_excerpts(
16936                buffer.clone(),
16937                [
16938                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16939                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16940                ],
16941                cx,
16942            )
16943            .into_iter()
16944            .next();
16945        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16946        multibuffer
16947    });
16948
16949    let editor = cx.add_window(|window, cx| {
16950        let mut editor = build_editor(multibuffer.clone(), window, cx);
16951        let snapshot = editor.snapshot(window, cx);
16952        editor.begin_selection(
16953            Point::new(1, 3).to_display_point(&snapshot),
16954            false,
16955            1,
16956            window,
16957            cx,
16958        );
16959        assert_eq!(
16960            editor.selections.ranges(&editor.display_snapshot(cx)),
16961            [Point::new(1, 3)..Point::new(1, 3)]
16962        );
16963        editor
16964    });
16965
16966    multibuffer.update(cx, |multibuffer, cx| {
16967        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16968    });
16969    _ = editor.update(cx, |editor, window, cx| {
16970        assert_eq!(
16971            editor.selections.ranges(&editor.display_snapshot(cx)),
16972            [Point::new(0, 0)..Point::new(0, 0)]
16973        );
16974
16975        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16976        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16977        assert_eq!(
16978            editor.selections.ranges(&editor.display_snapshot(cx)),
16979            [Point::new(0, 3)..Point::new(0, 3)]
16980        );
16981        assert!(editor.selections.pending_anchor().is_some());
16982    });
16983}
16984
16985#[gpui::test]
16986async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16987    init_test(cx, |_| {});
16988
16989    let language = Arc::new(
16990        Language::new(
16991            LanguageConfig {
16992                brackets: BracketPairConfig {
16993                    pairs: vec![
16994                        BracketPair {
16995                            start: "{".to_string(),
16996                            end: "}".to_string(),
16997                            close: true,
16998                            surround: true,
16999                            newline: true,
17000                        },
17001                        BracketPair {
17002                            start: "/* ".to_string(),
17003                            end: " */".to_string(),
17004                            close: true,
17005                            surround: true,
17006                            newline: true,
17007                        },
17008                    ],
17009                    ..Default::default()
17010                },
17011                ..Default::default()
17012            },
17013            Some(tree_sitter_rust::LANGUAGE.into()),
17014        )
17015        .with_indents_query("")
17016        .unwrap(),
17017    );
17018
17019    let text = concat!(
17020        "{   }\n",     //
17021        "  x\n",       //
17022        "  /*   */\n", //
17023        "x\n",         //
17024        "{{} }\n",     //
17025    );
17026
17027    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
17028    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17029    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
17030    editor
17031        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
17032        .await;
17033
17034    editor.update_in(cx, |editor, window, cx| {
17035        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17036            s.select_display_ranges([
17037                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
17038                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
17039                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
17040            ])
17041        });
17042        editor.newline(&Newline, window, cx);
17043
17044        assert_eq!(
17045            editor.buffer().read(cx).read(cx).text(),
17046            concat!(
17047                "{ \n",    // Suppress rustfmt
17048                "\n",      //
17049                "}\n",     //
17050                "  x\n",   //
17051                "  /* \n", //
17052                "  \n",    //
17053                "  */\n",  //
17054                "x\n",     //
17055                "{{} \n",  //
17056                "}\n",     //
17057            )
17058        );
17059    });
17060}
17061
17062#[gpui::test]
17063fn test_highlighted_ranges(cx: &mut TestAppContext) {
17064    init_test(cx, |_| {});
17065
17066    let editor = cx.add_window(|window, cx| {
17067        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
17068        build_editor(buffer, window, cx)
17069    });
17070
17071    _ = editor.update(cx, |editor, window, cx| {
17072        struct Type1;
17073        struct Type2;
17074
17075        let buffer = editor.buffer.read(cx).snapshot(cx);
17076
17077        let anchor_range =
17078            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
17079
17080        editor.highlight_background::<Type1>(
17081            &[
17082                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
17083                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
17084                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
17085                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
17086            ],
17087            |_, _| Hsla::red(),
17088            cx,
17089        );
17090        editor.highlight_background::<Type2>(
17091            &[
17092                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
17093                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
17094                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
17095                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
17096            ],
17097            |_, _| Hsla::green(),
17098            cx,
17099        );
17100
17101        let snapshot = editor.snapshot(window, cx);
17102        let highlighted_ranges = editor.sorted_background_highlights_in_range(
17103            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
17104            &snapshot,
17105            cx.theme(),
17106        );
17107        assert_eq!(
17108            highlighted_ranges,
17109            &[
17110                (
17111                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17112                    Hsla::green(),
17113                ),
17114                (
17115                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17116                    Hsla::red(),
17117                ),
17118                (
17119                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17120                    Hsla::green(),
17121                ),
17122                (
17123                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17124                    Hsla::red(),
17125                ),
17126            ]
17127        );
17128        assert_eq!(
17129            editor.sorted_background_highlights_in_range(
17130                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17131                &snapshot,
17132                cx.theme(),
17133            ),
17134            &[(
17135                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17136                Hsla::red(),
17137            )]
17138        );
17139    });
17140}
17141
17142#[gpui::test]
17143async fn test_following(cx: &mut TestAppContext) {
17144    init_test(cx, |_| {});
17145
17146    let fs = FakeFs::new(cx.executor());
17147    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17148
17149    let buffer = project.update(cx, |project, cx| {
17150        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17151        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17152    });
17153    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17154    let follower = cx.update(|cx| {
17155        cx.open_window(
17156            WindowOptions {
17157                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17158                    gpui::Point::new(px(0.), px(0.)),
17159                    gpui::Point::new(px(10.), px(80.)),
17160                ))),
17161                ..Default::default()
17162            },
17163            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17164        )
17165        .unwrap()
17166    });
17167
17168    let is_still_following = Rc::new(RefCell::new(true));
17169    let follower_edit_event_count = Rc::new(RefCell::new(0));
17170    let pending_update = Rc::new(RefCell::new(None));
17171    let leader_entity = leader.root(cx).unwrap();
17172    let follower_entity = follower.root(cx).unwrap();
17173    _ = follower.update(cx, {
17174        let update = pending_update.clone();
17175        let is_still_following = is_still_following.clone();
17176        let follower_edit_event_count = follower_edit_event_count.clone();
17177        |_, window, cx| {
17178            cx.subscribe_in(
17179                &leader_entity,
17180                window,
17181                move |_, leader, event, window, cx| {
17182                    leader.read(cx).add_event_to_update_proto(
17183                        event,
17184                        &mut update.borrow_mut(),
17185                        window,
17186                        cx,
17187                    );
17188                },
17189            )
17190            .detach();
17191
17192            cx.subscribe_in(
17193                &follower_entity,
17194                window,
17195                move |_, _, event: &EditorEvent, _window, _cx| {
17196                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17197                        *is_still_following.borrow_mut() = false;
17198                    }
17199
17200                    if let EditorEvent::BufferEdited = event {
17201                        *follower_edit_event_count.borrow_mut() += 1;
17202                    }
17203                },
17204            )
17205            .detach();
17206        }
17207    });
17208
17209    // Update the selections only
17210    _ = leader.update(cx, |leader, window, cx| {
17211        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17212            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17213        });
17214    });
17215    follower
17216        .update(cx, |follower, window, cx| {
17217            follower.apply_update_proto(
17218                &project,
17219                pending_update.borrow_mut().take().unwrap(),
17220                window,
17221                cx,
17222            )
17223        })
17224        .unwrap()
17225        .await
17226        .unwrap();
17227    _ = follower.update(cx, |follower, _, cx| {
17228        assert_eq!(
17229            follower.selections.ranges(&follower.display_snapshot(cx)),
17230            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17231        );
17232    });
17233    assert!(*is_still_following.borrow());
17234    assert_eq!(*follower_edit_event_count.borrow(), 0);
17235
17236    // Update the scroll position only
17237    _ = leader.update(cx, |leader, window, cx| {
17238        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17239    });
17240    follower
17241        .update(cx, |follower, window, cx| {
17242            follower.apply_update_proto(
17243                &project,
17244                pending_update.borrow_mut().take().unwrap(),
17245                window,
17246                cx,
17247            )
17248        })
17249        .unwrap()
17250        .await
17251        .unwrap();
17252    assert_eq!(
17253        follower
17254            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17255            .unwrap(),
17256        gpui::Point::new(1.5, 3.5)
17257    );
17258    assert!(*is_still_following.borrow());
17259    assert_eq!(*follower_edit_event_count.borrow(), 0);
17260
17261    // Update the selections and scroll position. The follower's scroll position is updated
17262    // via autoscroll, not via the leader's exact scroll position.
17263    _ = leader.update(cx, |leader, window, cx| {
17264        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17265            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17266        });
17267        leader.request_autoscroll(Autoscroll::newest(), cx);
17268        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17269    });
17270    follower
17271        .update(cx, |follower, window, cx| {
17272            follower.apply_update_proto(
17273                &project,
17274                pending_update.borrow_mut().take().unwrap(),
17275                window,
17276                cx,
17277            )
17278        })
17279        .unwrap()
17280        .await
17281        .unwrap();
17282    _ = follower.update(cx, |follower, _, cx| {
17283        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17284        assert_eq!(
17285            follower.selections.ranges(&follower.display_snapshot(cx)),
17286            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17287        );
17288    });
17289    assert!(*is_still_following.borrow());
17290
17291    // Creating a pending selection that precedes another selection
17292    _ = leader.update(cx, |leader, window, cx| {
17293        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17294            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17295        });
17296        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17297    });
17298    follower
17299        .update(cx, |follower, window, cx| {
17300            follower.apply_update_proto(
17301                &project,
17302                pending_update.borrow_mut().take().unwrap(),
17303                window,
17304                cx,
17305            )
17306        })
17307        .unwrap()
17308        .await
17309        .unwrap();
17310    _ = follower.update(cx, |follower, _, cx| {
17311        assert_eq!(
17312            follower.selections.ranges(&follower.display_snapshot(cx)),
17313            vec![
17314                MultiBufferOffset(0)..MultiBufferOffset(0),
17315                MultiBufferOffset(1)..MultiBufferOffset(1)
17316            ]
17317        );
17318    });
17319    assert!(*is_still_following.borrow());
17320
17321    // Extend the pending selection so that it surrounds another selection
17322    _ = leader.update(cx, |leader, window, cx| {
17323        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17324    });
17325    follower
17326        .update(cx, |follower, window, cx| {
17327            follower.apply_update_proto(
17328                &project,
17329                pending_update.borrow_mut().take().unwrap(),
17330                window,
17331                cx,
17332            )
17333        })
17334        .unwrap()
17335        .await
17336        .unwrap();
17337    _ = follower.update(cx, |follower, _, cx| {
17338        assert_eq!(
17339            follower.selections.ranges(&follower.display_snapshot(cx)),
17340            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17341        );
17342    });
17343
17344    // Scrolling locally breaks the follow
17345    _ = follower.update(cx, |follower, window, cx| {
17346        let top_anchor = follower
17347            .buffer()
17348            .read(cx)
17349            .read(cx)
17350            .anchor_after(MultiBufferOffset(0));
17351        follower.set_scroll_anchor(
17352            ScrollAnchor {
17353                anchor: top_anchor,
17354                offset: gpui::Point::new(0.0, 0.5),
17355            },
17356            window,
17357            cx,
17358        );
17359    });
17360    assert!(!(*is_still_following.borrow()));
17361}
17362
17363#[gpui::test]
17364async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17365    init_test(cx, |_| {});
17366
17367    let fs = FakeFs::new(cx.executor());
17368    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17369    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17370    let pane = workspace
17371        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17372        .unwrap();
17373
17374    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17375
17376    let leader = pane.update_in(cx, |_, window, cx| {
17377        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17378        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17379    });
17380
17381    // Start following the editor when it has no excerpts.
17382    let mut state_message =
17383        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17384    let workspace_entity = workspace.root(cx).unwrap();
17385    let follower_1 = cx
17386        .update_window(*workspace.deref(), |_, window, cx| {
17387            Editor::from_state_proto(
17388                workspace_entity,
17389                ViewId {
17390                    creator: CollaboratorId::PeerId(PeerId::default()),
17391                    id: 0,
17392                },
17393                &mut state_message,
17394                window,
17395                cx,
17396            )
17397        })
17398        .unwrap()
17399        .unwrap()
17400        .await
17401        .unwrap();
17402
17403    let update_message = Rc::new(RefCell::new(None));
17404    follower_1.update_in(cx, {
17405        let update = update_message.clone();
17406        |_, window, cx| {
17407            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17408                leader.read(cx).add_event_to_update_proto(
17409                    event,
17410                    &mut update.borrow_mut(),
17411                    window,
17412                    cx,
17413                );
17414            })
17415            .detach();
17416        }
17417    });
17418
17419    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17420        (
17421            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17422            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17423        )
17424    });
17425
17426    // Insert some excerpts.
17427    leader.update(cx, |leader, cx| {
17428        leader.buffer.update(cx, |multibuffer, cx| {
17429            multibuffer.set_excerpts_for_path(
17430                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17431                buffer_1.clone(),
17432                vec![
17433                    Point::row_range(0..3),
17434                    Point::row_range(1..6),
17435                    Point::row_range(12..15),
17436                ],
17437                0,
17438                cx,
17439            );
17440            multibuffer.set_excerpts_for_path(
17441                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17442                buffer_2.clone(),
17443                vec![Point::row_range(0..6), Point::row_range(8..12)],
17444                0,
17445                cx,
17446            );
17447        });
17448    });
17449
17450    // Apply the update of adding the excerpts.
17451    follower_1
17452        .update_in(cx, |follower, window, cx| {
17453            follower.apply_update_proto(
17454                &project,
17455                update_message.borrow().clone().unwrap(),
17456                window,
17457                cx,
17458            )
17459        })
17460        .await
17461        .unwrap();
17462    assert_eq!(
17463        follower_1.update(cx, |editor, cx| editor.text(cx)),
17464        leader.update(cx, |editor, cx| editor.text(cx))
17465    );
17466    update_message.borrow_mut().take();
17467
17468    // Start following separately after it already has excerpts.
17469    let mut state_message =
17470        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17471    let workspace_entity = workspace.root(cx).unwrap();
17472    let follower_2 = cx
17473        .update_window(*workspace.deref(), |_, window, cx| {
17474            Editor::from_state_proto(
17475                workspace_entity,
17476                ViewId {
17477                    creator: CollaboratorId::PeerId(PeerId::default()),
17478                    id: 0,
17479                },
17480                &mut state_message,
17481                window,
17482                cx,
17483            )
17484        })
17485        .unwrap()
17486        .unwrap()
17487        .await
17488        .unwrap();
17489    assert_eq!(
17490        follower_2.update(cx, |editor, cx| editor.text(cx)),
17491        leader.update(cx, |editor, cx| editor.text(cx))
17492    );
17493
17494    // Remove some excerpts.
17495    leader.update(cx, |leader, cx| {
17496        leader.buffer.update(cx, |multibuffer, cx| {
17497            let excerpt_ids = multibuffer.excerpt_ids();
17498            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17499            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17500        });
17501    });
17502
17503    // Apply the update of removing the excerpts.
17504    follower_1
17505        .update_in(cx, |follower, window, cx| {
17506            follower.apply_update_proto(
17507                &project,
17508                update_message.borrow().clone().unwrap(),
17509                window,
17510                cx,
17511            )
17512        })
17513        .await
17514        .unwrap();
17515    follower_2
17516        .update_in(cx, |follower, window, cx| {
17517            follower.apply_update_proto(
17518                &project,
17519                update_message.borrow().clone().unwrap(),
17520                window,
17521                cx,
17522            )
17523        })
17524        .await
17525        .unwrap();
17526    update_message.borrow_mut().take();
17527    assert_eq!(
17528        follower_1.update(cx, |editor, cx| editor.text(cx)),
17529        leader.update(cx, |editor, cx| editor.text(cx))
17530    );
17531}
17532
17533#[gpui::test]
17534async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17535    init_test(cx, |_| {});
17536
17537    let mut cx = EditorTestContext::new(cx).await;
17538    let lsp_store =
17539        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17540
17541    cx.set_state(indoc! {"
17542        ˇfn func(abc def: i32) -> u32 {
17543        }
17544    "});
17545
17546    cx.update(|_, cx| {
17547        lsp_store.update(cx, |lsp_store, cx| {
17548            lsp_store
17549                .update_diagnostics(
17550                    LanguageServerId(0),
17551                    lsp::PublishDiagnosticsParams {
17552                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17553                        version: None,
17554                        diagnostics: vec![
17555                            lsp::Diagnostic {
17556                                range: lsp::Range::new(
17557                                    lsp::Position::new(0, 11),
17558                                    lsp::Position::new(0, 12),
17559                                ),
17560                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17561                                ..Default::default()
17562                            },
17563                            lsp::Diagnostic {
17564                                range: lsp::Range::new(
17565                                    lsp::Position::new(0, 12),
17566                                    lsp::Position::new(0, 15),
17567                                ),
17568                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17569                                ..Default::default()
17570                            },
17571                            lsp::Diagnostic {
17572                                range: lsp::Range::new(
17573                                    lsp::Position::new(0, 25),
17574                                    lsp::Position::new(0, 28),
17575                                ),
17576                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17577                                ..Default::default()
17578                            },
17579                        ],
17580                    },
17581                    None,
17582                    DiagnosticSourceKind::Pushed,
17583                    &[],
17584                    cx,
17585                )
17586                .unwrap()
17587        });
17588    });
17589
17590    executor.run_until_parked();
17591
17592    cx.update_editor(|editor, window, cx| {
17593        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17594    });
17595
17596    cx.assert_editor_state(indoc! {"
17597        fn func(abc def: i32) -> ˇu32 {
17598        }
17599    "});
17600
17601    cx.update_editor(|editor, window, cx| {
17602        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17603    });
17604
17605    cx.assert_editor_state(indoc! {"
17606        fn func(abc ˇdef: i32) -> u32 {
17607        }
17608    "});
17609
17610    cx.update_editor(|editor, window, cx| {
17611        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17612    });
17613
17614    cx.assert_editor_state(indoc! {"
17615        fn func(abcˇ def: i32) -> u32 {
17616        }
17617    "});
17618
17619    cx.update_editor(|editor, window, cx| {
17620        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17621    });
17622
17623    cx.assert_editor_state(indoc! {"
17624        fn func(abc def: i32) -> ˇu32 {
17625        }
17626    "});
17627}
17628
17629#[gpui::test]
17630async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17631    init_test(cx, |_| {});
17632
17633    let mut cx = EditorTestContext::new(cx).await;
17634
17635    let diff_base = r#"
17636        use some::mod;
17637
17638        const A: u32 = 42;
17639
17640        fn main() {
17641            println!("hello");
17642
17643            println!("world");
17644        }
17645        "#
17646    .unindent();
17647
17648    // Edits are modified, removed, modified, added
17649    cx.set_state(
17650        &r#"
17651        use some::modified;
17652
17653        ˇ
17654        fn main() {
17655            println!("hello there");
17656
17657            println!("around the");
17658            println!("world");
17659        }
17660        "#
17661        .unindent(),
17662    );
17663
17664    cx.set_head_text(&diff_base);
17665    executor.run_until_parked();
17666
17667    cx.update_editor(|editor, window, cx| {
17668        //Wrap around the bottom of the buffer
17669        for _ in 0..3 {
17670            editor.go_to_next_hunk(&GoToHunk, window, cx);
17671        }
17672    });
17673
17674    cx.assert_editor_state(
17675        &r#"
17676        ˇuse some::modified;
17677
17678
17679        fn main() {
17680            println!("hello there");
17681
17682            println!("around the");
17683            println!("world");
17684        }
17685        "#
17686        .unindent(),
17687    );
17688
17689    cx.update_editor(|editor, window, cx| {
17690        //Wrap around the top of the buffer
17691        for _ in 0..2 {
17692            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17693        }
17694    });
17695
17696    cx.assert_editor_state(
17697        &r#"
17698        use some::modified;
17699
17700
17701        fn main() {
17702        ˇ    println!("hello there");
17703
17704            println!("around the");
17705            println!("world");
17706        }
17707        "#
17708        .unindent(),
17709    );
17710
17711    cx.update_editor(|editor, window, cx| {
17712        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17713    });
17714
17715    cx.assert_editor_state(
17716        &r#"
17717        use some::modified;
17718
17719        ˇ
17720        fn main() {
17721            println!("hello there");
17722
17723            println!("around the");
17724            println!("world");
17725        }
17726        "#
17727        .unindent(),
17728    );
17729
17730    cx.update_editor(|editor, window, cx| {
17731        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17732    });
17733
17734    cx.assert_editor_state(
17735        &r#"
17736        ˇuse some::modified;
17737
17738
17739        fn main() {
17740            println!("hello there");
17741
17742            println!("around the");
17743            println!("world");
17744        }
17745        "#
17746        .unindent(),
17747    );
17748
17749    cx.update_editor(|editor, window, cx| {
17750        for _ in 0..2 {
17751            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17752        }
17753    });
17754
17755    cx.assert_editor_state(
17756        &r#"
17757        use some::modified;
17758
17759
17760        fn main() {
17761        ˇ    println!("hello there");
17762
17763            println!("around the");
17764            println!("world");
17765        }
17766        "#
17767        .unindent(),
17768    );
17769
17770    cx.update_editor(|editor, window, cx| {
17771        editor.fold(&Fold, window, cx);
17772    });
17773
17774    cx.update_editor(|editor, window, cx| {
17775        editor.go_to_next_hunk(&GoToHunk, window, cx);
17776    });
17777
17778    cx.assert_editor_state(
17779        &r#"
17780        ˇuse some::modified;
17781
17782
17783        fn main() {
17784            println!("hello there");
17785
17786            println!("around the");
17787            println!("world");
17788        }
17789        "#
17790        .unindent(),
17791    );
17792}
17793
17794#[test]
17795fn test_split_words() {
17796    fn split(text: &str) -> Vec<&str> {
17797        split_words(text).collect()
17798    }
17799
17800    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17801    assert_eq!(split("hello_world"), &["hello_", "world"]);
17802    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17803    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17804    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17805    assert_eq!(split("helloworld"), &["helloworld"]);
17806
17807    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17808}
17809
17810#[test]
17811fn test_split_words_for_snippet_prefix() {
17812    fn split(text: &str) -> Vec<&str> {
17813        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17814    }
17815
17816    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17817    assert_eq!(split("hello_world"), &["hello_world"]);
17818    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17819    assert_eq!(split("Hello_World"), &["Hello_World"]);
17820    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17821    assert_eq!(split("helloworld"), &["helloworld"]);
17822    assert_eq!(
17823        split("this@is!@#$^many   . symbols"),
17824        &[
17825            "symbols",
17826            " symbols",
17827            ". symbols",
17828            " . symbols",
17829            "  . symbols",
17830            "   . symbols",
17831            "many   . symbols",
17832            "^many   . symbols",
17833            "$^many   . symbols",
17834            "#$^many   . symbols",
17835            "@#$^many   . symbols",
17836            "!@#$^many   . symbols",
17837            "is!@#$^many   . symbols",
17838            "@is!@#$^many   . symbols",
17839            "this@is!@#$^many   . symbols",
17840        ],
17841    );
17842    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17843}
17844
17845#[gpui::test]
17846async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17847    init_test(cx, |_| {});
17848
17849    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17850
17851    #[track_caller]
17852    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17853        let _state_context = cx.set_state(before);
17854        cx.run_until_parked();
17855        cx.update_editor(|editor, window, cx| {
17856            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17857        });
17858        cx.run_until_parked();
17859        cx.assert_editor_state(after);
17860    }
17861
17862    // Outside bracket jumps to outside of matching bracket
17863    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17864    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17865
17866    // Inside bracket jumps to inside of matching bracket
17867    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17868    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17869
17870    // When outside a bracket and inside, favor jumping to the inside bracket
17871    assert(
17872        "console.log('foo', [1, 2, 3]ˇ);",
17873        "console.log('foo', ˇ[1, 2, 3]);",
17874        &mut cx,
17875    );
17876    assert(
17877        "console.log(ˇ'foo', [1, 2, 3]);",
17878        "console.log('foo'ˇ, [1, 2, 3]);",
17879        &mut cx,
17880    );
17881
17882    // Bias forward if two options are equally likely
17883    assert(
17884        "let result = curried_fun()ˇ();",
17885        "let result = curried_fun()()ˇ;",
17886        &mut cx,
17887    );
17888
17889    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17890    assert(
17891        indoc! {"
17892            function test() {
17893                console.log('test')ˇ
17894            }"},
17895        indoc! {"
17896            function test() {
17897                console.logˇ('test')
17898            }"},
17899        &mut cx,
17900    );
17901}
17902
17903#[gpui::test]
17904async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
17905    init_test(cx, |_| {});
17906    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
17907    language_registry.add(markdown_lang());
17908    language_registry.add(rust_lang());
17909    let buffer = cx.new(|cx| {
17910        let mut buffer = language::Buffer::local(
17911            indoc! {"
17912            ```rs
17913            impl Worktree {
17914                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17915                }
17916            }
17917            ```
17918        "},
17919            cx,
17920        );
17921        buffer.set_language_registry(language_registry.clone());
17922        buffer.set_language(Some(markdown_lang()), cx);
17923        buffer
17924    });
17925    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17926    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17927    cx.executor().run_until_parked();
17928    _ = editor.update(cx, |editor, window, cx| {
17929        // Case 1: Test outer enclosing brackets
17930        select_ranges(
17931            editor,
17932            &indoc! {"
17933                ```rs
17934                impl Worktree {
17935                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17936                    }
1793717938                ```
17939            "},
17940            window,
17941            cx,
17942        );
17943        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17944        assert_text_with_selections(
17945            editor,
17946            &indoc! {"
17947                ```rs
17948                impl Worktree ˇ{
17949                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17950                    }
17951                }
17952                ```
17953            "},
17954            cx,
17955        );
17956        // Case 2: Test inner enclosing brackets
17957        select_ranges(
17958            editor,
17959            &indoc! {"
17960                ```rs
17961                impl Worktree {
17962                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1796317964                }
17965                ```
17966            "},
17967            window,
17968            cx,
17969        );
17970        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17971        assert_text_with_selections(
17972            editor,
17973            &indoc! {"
17974                ```rs
17975                impl Worktree {
17976                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
17977                    }
17978                }
17979                ```
17980            "},
17981            cx,
17982        );
17983    });
17984}
17985
17986#[gpui::test]
17987async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17988    init_test(cx, |_| {});
17989
17990    let fs = FakeFs::new(cx.executor());
17991    fs.insert_tree(
17992        path!("/a"),
17993        json!({
17994            "main.rs": "fn main() { let a = 5; }",
17995            "other.rs": "// Test file",
17996        }),
17997    )
17998    .await;
17999    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18000
18001    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18002    language_registry.add(Arc::new(Language::new(
18003        LanguageConfig {
18004            name: "Rust".into(),
18005            matcher: LanguageMatcher {
18006                path_suffixes: vec!["rs".to_string()],
18007                ..Default::default()
18008            },
18009            brackets: BracketPairConfig {
18010                pairs: vec![BracketPair {
18011                    start: "{".to_string(),
18012                    end: "}".to_string(),
18013                    close: true,
18014                    surround: true,
18015                    newline: true,
18016                }],
18017                disabled_scopes_by_bracket_ix: Vec::new(),
18018            },
18019            ..Default::default()
18020        },
18021        Some(tree_sitter_rust::LANGUAGE.into()),
18022    )));
18023    let mut fake_servers = language_registry.register_fake_lsp(
18024        "Rust",
18025        FakeLspAdapter {
18026            capabilities: lsp::ServerCapabilities {
18027                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18028                    first_trigger_character: "{".to_string(),
18029                    more_trigger_character: None,
18030                }),
18031                ..Default::default()
18032            },
18033            ..Default::default()
18034        },
18035    );
18036
18037    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18038
18039    let cx = &mut VisualTestContext::from_window(*workspace, cx);
18040
18041    let worktree_id = workspace
18042        .update(cx, |workspace, _, cx| {
18043            workspace.project().update(cx, |project, cx| {
18044                project.worktrees(cx).next().unwrap().read(cx).id()
18045            })
18046        })
18047        .unwrap();
18048
18049    let buffer = project
18050        .update(cx, |project, cx| {
18051            project.open_local_buffer(path!("/a/main.rs"), cx)
18052        })
18053        .await
18054        .unwrap();
18055    let editor_handle = workspace
18056        .update(cx, |workspace, window, cx| {
18057            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
18058        })
18059        .unwrap()
18060        .await
18061        .unwrap()
18062        .downcast::<Editor>()
18063        .unwrap();
18064
18065    cx.executor().start_waiting();
18066    let fake_server = fake_servers.next().await.unwrap();
18067
18068    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
18069        |params, _| async move {
18070            assert_eq!(
18071                params.text_document_position.text_document.uri,
18072                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
18073            );
18074            assert_eq!(
18075                params.text_document_position.position,
18076                lsp::Position::new(0, 21),
18077            );
18078
18079            Ok(Some(vec![lsp::TextEdit {
18080                new_text: "]".to_string(),
18081                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18082            }]))
18083        },
18084    );
18085
18086    editor_handle.update_in(cx, |editor, window, cx| {
18087        window.focus(&editor.focus_handle(cx));
18088        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18089            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
18090        });
18091        editor.handle_input("{", window, cx);
18092    });
18093
18094    cx.executor().run_until_parked();
18095
18096    buffer.update(cx, |buffer, _| {
18097        assert_eq!(
18098            buffer.text(),
18099            "fn main() { let a = {5}; }",
18100            "No extra braces from on type formatting should appear in the buffer"
18101        )
18102    });
18103}
18104
18105#[gpui::test(iterations = 20, seeds(31))]
18106async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18107    init_test(cx, |_| {});
18108
18109    let mut cx = EditorLspTestContext::new_rust(
18110        lsp::ServerCapabilities {
18111            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18112                first_trigger_character: ".".to_string(),
18113                more_trigger_character: None,
18114            }),
18115            ..Default::default()
18116        },
18117        cx,
18118    )
18119    .await;
18120
18121    cx.update_buffer(|buffer, _| {
18122        // This causes autoindent to be async.
18123        buffer.set_sync_parse_timeout(Duration::ZERO)
18124    });
18125
18126    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18127    cx.simulate_keystroke("\n");
18128    cx.run_until_parked();
18129
18130    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18131    let mut request =
18132        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18133            let buffer_cloned = buffer_cloned.clone();
18134            async move {
18135                buffer_cloned.update(&mut cx, |buffer, _| {
18136                    assert_eq!(
18137                        buffer.text(),
18138                        "fn c() {\n    d()\n        .\n}\n",
18139                        "OnTypeFormatting should triggered after autoindent applied"
18140                    )
18141                })?;
18142
18143                Ok(Some(vec![]))
18144            }
18145        });
18146
18147    cx.simulate_keystroke(".");
18148    cx.run_until_parked();
18149
18150    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18151    assert!(request.next().await.is_some());
18152    request.close();
18153    assert!(request.next().await.is_none());
18154}
18155
18156#[gpui::test]
18157async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18158    init_test(cx, |_| {});
18159
18160    let fs = FakeFs::new(cx.executor());
18161    fs.insert_tree(
18162        path!("/a"),
18163        json!({
18164            "main.rs": "fn main() { let a = 5; }",
18165            "other.rs": "// Test file",
18166        }),
18167    )
18168    .await;
18169
18170    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18171
18172    let server_restarts = Arc::new(AtomicUsize::new(0));
18173    let closure_restarts = Arc::clone(&server_restarts);
18174    let language_server_name = "test language server";
18175    let language_name: LanguageName = "Rust".into();
18176
18177    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18178    language_registry.add(Arc::new(Language::new(
18179        LanguageConfig {
18180            name: language_name.clone(),
18181            matcher: LanguageMatcher {
18182                path_suffixes: vec!["rs".to_string()],
18183                ..Default::default()
18184            },
18185            ..Default::default()
18186        },
18187        Some(tree_sitter_rust::LANGUAGE.into()),
18188    )));
18189    let mut fake_servers = language_registry.register_fake_lsp(
18190        "Rust",
18191        FakeLspAdapter {
18192            name: language_server_name,
18193            initialization_options: Some(json!({
18194                "testOptionValue": true
18195            })),
18196            initializer: Some(Box::new(move |fake_server| {
18197                let task_restarts = Arc::clone(&closure_restarts);
18198                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18199                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18200                    futures::future::ready(Ok(()))
18201                });
18202            })),
18203            ..Default::default()
18204        },
18205    );
18206
18207    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18208    let _buffer = project
18209        .update(cx, |project, cx| {
18210            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18211        })
18212        .await
18213        .unwrap();
18214    let _fake_server = fake_servers.next().await.unwrap();
18215    update_test_language_settings(cx, |language_settings| {
18216        language_settings.languages.0.insert(
18217            language_name.clone().0,
18218            LanguageSettingsContent {
18219                tab_size: NonZeroU32::new(8),
18220                ..Default::default()
18221            },
18222        );
18223    });
18224    cx.executor().run_until_parked();
18225    assert_eq!(
18226        server_restarts.load(atomic::Ordering::Acquire),
18227        0,
18228        "Should not restart LSP server on an unrelated change"
18229    );
18230
18231    update_test_project_settings(cx, |project_settings| {
18232        project_settings.lsp.insert(
18233            "Some other server name".into(),
18234            LspSettings {
18235                binary: None,
18236                settings: None,
18237                initialization_options: Some(json!({
18238                    "some other init value": false
18239                })),
18240                enable_lsp_tasks: false,
18241                fetch: None,
18242            },
18243        );
18244    });
18245    cx.executor().run_until_parked();
18246    assert_eq!(
18247        server_restarts.load(atomic::Ordering::Acquire),
18248        0,
18249        "Should not restart LSP server on an unrelated LSP settings change"
18250    );
18251
18252    update_test_project_settings(cx, |project_settings| {
18253        project_settings.lsp.insert(
18254            language_server_name.into(),
18255            LspSettings {
18256                binary: None,
18257                settings: None,
18258                initialization_options: Some(json!({
18259                    "anotherInitValue": false
18260                })),
18261                enable_lsp_tasks: false,
18262                fetch: None,
18263            },
18264        );
18265    });
18266    cx.executor().run_until_parked();
18267    assert_eq!(
18268        server_restarts.load(atomic::Ordering::Acquire),
18269        1,
18270        "Should restart LSP server on a related LSP settings change"
18271    );
18272
18273    update_test_project_settings(cx, |project_settings| {
18274        project_settings.lsp.insert(
18275            language_server_name.into(),
18276            LspSettings {
18277                binary: None,
18278                settings: None,
18279                initialization_options: Some(json!({
18280                    "anotherInitValue": false
18281                })),
18282                enable_lsp_tasks: false,
18283                fetch: None,
18284            },
18285        );
18286    });
18287    cx.executor().run_until_parked();
18288    assert_eq!(
18289        server_restarts.load(atomic::Ordering::Acquire),
18290        1,
18291        "Should not restart LSP server on a related LSP settings change that is the same"
18292    );
18293
18294    update_test_project_settings(cx, |project_settings| {
18295        project_settings.lsp.insert(
18296            language_server_name.into(),
18297            LspSettings {
18298                binary: None,
18299                settings: None,
18300                initialization_options: None,
18301                enable_lsp_tasks: false,
18302                fetch: None,
18303            },
18304        );
18305    });
18306    cx.executor().run_until_parked();
18307    assert_eq!(
18308        server_restarts.load(atomic::Ordering::Acquire),
18309        2,
18310        "Should restart LSP server on another related LSP settings change"
18311    );
18312}
18313
18314#[gpui::test]
18315async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18316    init_test(cx, |_| {});
18317
18318    let mut cx = EditorLspTestContext::new_rust(
18319        lsp::ServerCapabilities {
18320            completion_provider: Some(lsp::CompletionOptions {
18321                trigger_characters: Some(vec![".".to_string()]),
18322                resolve_provider: Some(true),
18323                ..Default::default()
18324            }),
18325            ..Default::default()
18326        },
18327        cx,
18328    )
18329    .await;
18330
18331    cx.set_state("fn main() { let a = 2ˇ; }");
18332    cx.simulate_keystroke(".");
18333    let completion_item = lsp::CompletionItem {
18334        label: "some".into(),
18335        kind: Some(lsp::CompletionItemKind::SNIPPET),
18336        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18337        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18338            kind: lsp::MarkupKind::Markdown,
18339            value: "```rust\nSome(2)\n```".to_string(),
18340        })),
18341        deprecated: Some(false),
18342        sort_text: Some("fffffff2".to_string()),
18343        filter_text: Some("some".to_string()),
18344        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18345        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18346            range: lsp::Range {
18347                start: lsp::Position {
18348                    line: 0,
18349                    character: 22,
18350                },
18351                end: lsp::Position {
18352                    line: 0,
18353                    character: 22,
18354                },
18355            },
18356            new_text: "Some(2)".to_string(),
18357        })),
18358        additional_text_edits: Some(vec![lsp::TextEdit {
18359            range: lsp::Range {
18360                start: lsp::Position {
18361                    line: 0,
18362                    character: 20,
18363                },
18364                end: lsp::Position {
18365                    line: 0,
18366                    character: 22,
18367                },
18368            },
18369            new_text: "".to_string(),
18370        }]),
18371        ..Default::default()
18372    };
18373
18374    let closure_completion_item = completion_item.clone();
18375    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18376        let task_completion_item = closure_completion_item.clone();
18377        async move {
18378            Ok(Some(lsp::CompletionResponse::Array(vec![
18379                task_completion_item,
18380            ])))
18381        }
18382    });
18383
18384    request.next().await;
18385
18386    cx.condition(|editor, _| editor.context_menu_visible())
18387        .await;
18388    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18389        editor
18390            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18391            .unwrap()
18392    });
18393    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18394
18395    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18396        let task_completion_item = completion_item.clone();
18397        async move { Ok(task_completion_item) }
18398    })
18399    .next()
18400    .await
18401    .unwrap();
18402    apply_additional_edits.await.unwrap();
18403    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18404}
18405
18406#[gpui::test]
18407async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18408    init_test(cx, |_| {});
18409
18410    let mut cx = EditorLspTestContext::new_rust(
18411        lsp::ServerCapabilities {
18412            completion_provider: Some(lsp::CompletionOptions {
18413                trigger_characters: Some(vec![".".to_string()]),
18414                resolve_provider: Some(true),
18415                ..Default::default()
18416            }),
18417            ..Default::default()
18418        },
18419        cx,
18420    )
18421    .await;
18422
18423    cx.set_state("fn main() { let a = 2ˇ; }");
18424    cx.simulate_keystroke(".");
18425
18426    let item1 = lsp::CompletionItem {
18427        label: "method id()".to_string(),
18428        filter_text: Some("id".to_string()),
18429        detail: None,
18430        documentation: None,
18431        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18432            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18433            new_text: ".id".to_string(),
18434        })),
18435        ..lsp::CompletionItem::default()
18436    };
18437
18438    let item2 = lsp::CompletionItem {
18439        label: "other".to_string(),
18440        filter_text: Some("other".to_string()),
18441        detail: None,
18442        documentation: None,
18443        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18444            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18445            new_text: ".other".to_string(),
18446        })),
18447        ..lsp::CompletionItem::default()
18448    };
18449
18450    let item1 = item1.clone();
18451    cx.set_request_handler::<lsp::request::Completion, _, _>({
18452        let item1 = item1.clone();
18453        move |_, _, _| {
18454            let item1 = item1.clone();
18455            let item2 = item2.clone();
18456            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18457        }
18458    })
18459    .next()
18460    .await;
18461
18462    cx.condition(|editor, _| editor.context_menu_visible())
18463        .await;
18464    cx.update_editor(|editor, _, _| {
18465        let context_menu = editor.context_menu.borrow_mut();
18466        let context_menu = context_menu
18467            .as_ref()
18468            .expect("Should have the context menu deployed");
18469        match context_menu {
18470            CodeContextMenu::Completions(completions_menu) => {
18471                let completions = completions_menu.completions.borrow_mut();
18472                assert_eq!(
18473                    completions
18474                        .iter()
18475                        .map(|completion| &completion.label.text)
18476                        .collect::<Vec<_>>(),
18477                    vec!["method id()", "other"]
18478                )
18479            }
18480            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18481        }
18482    });
18483
18484    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18485        let item1 = item1.clone();
18486        move |_, item_to_resolve, _| {
18487            let item1 = item1.clone();
18488            async move {
18489                if item1 == item_to_resolve {
18490                    Ok(lsp::CompletionItem {
18491                        label: "method id()".to_string(),
18492                        filter_text: Some("id".to_string()),
18493                        detail: Some("Now resolved!".to_string()),
18494                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18495                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18496                            range: lsp::Range::new(
18497                                lsp::Position::new(0, 22),
18498                                lsp::Position::new(0, 22),
18499                            ),
18500                            new_text: ".id".to_string(),
18501                        })),
18502                        ..lsp::CompletionItem::default()
18503                    })
18504                } else {
18505                    Ok(item_to_resolve)
18506                }
18507            }
18508        }
18509    })
18510    .next()
18511    .await
18512    .unwrap();
18513    cx.run_until_parked();
18514
18515    cx.update_editor(|editor, window, cx| {
18516        editor.context_menu_next(&Default::default(), window, cx);
18517    });
18518
18519    cx.update_editor(|editor, _, _| {
18520        let context_menu = editor.context_menu.borrow_mut();
18521        let context_menu = context_menu
18522            .as_ref()
18523            .expect("Should have the context menu deployed");
18524        match context_menu {
18525            CodeContextMenu::Completions(completions_menu) => {
18526                let completions = completions_menu.completions.borrow_mut();
18527                assert_eq!(
18528                    completions
18529                        .iter()
18530                        .map(|completion| &completion.label.text)
18531                        .collect::<Vec<_>>(),
18532                    vec!["method id() Now resolved!", "other"],
18533                    "Should update first completion label, but not second as the filter text did not match."
18534                );
18535            }
18536            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18537        }
18538    });
18539}
18540
18541#[gpui::test]
18542async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18543    init_test(cx, |_| {});
18544    let mut cx = EditorLspTestContext::new_rust(
18545        lsp::ServerCapabilities {
18546            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18547            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18548            completion_provider: Some(lsp::CompletionOptions {
18549                resolve_provider: Some(true),
18550                ..Default::default()
18551            }),
18552            ..Default::default()
18553        },
18554        cx,
18555    )
18556    .await;
18557    cx.set_state(indoc! {"
18558        struct TestStruct {
18559            field: i32
18560        }
18561
18562        fn mainˇ() {
18563            let unused_var = 42;
18564            let test_struct = TestStruct { field: 42 };
18565        }
18566    "});
18567    let symbol_range = cx.lsp_range(indoc! {"
18568        struct TestStruct {
18569            field: i32
18570        }
18571
18572        «fn main»() {
18573            let unused_var = 42;
18574            let test_struct = TestStruct { field: 42 };
18575        }
18576    "});
18577    let mut hover_requests =
18578        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18579            Ok(Some(lsp::Hover {
18580                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18581                    kind: lsp::MarkupKind::Markdown,
18582                    value: "Function documentation".to_string(),
18583                }),
18584                range: Some(symbol_range),
18585            }))
18586        });
18587
18588    // Case 1: Test that code action menu hide hover popover
18589    cx.dispatch_action(Hover);
18590    hover_requests.next().await;
18591    cx.condition(|editor, _| editor.hover_state.visible()).await;
18592    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18593        move |_, _, _| async move {
18594            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18595                lsp::CodeAction {
18596                    title: "Remove unused variable".to_string(),
18597                    kind: Some(CodeActionKind::QUICKFIX),
18598                    edit: Some(lsp::WorkspaceEdit {
18599                        changes: Some(
18600                            [(
18601                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18602                                vec![lsp::TextEdit {
18603                                    range: lsp::Range::new(
18604                                        lsp::Position::new(5, 4),
18605                                        lsp::Position::new(5, 27),
18606                                    ),
18607                                    new_text: "".to_string(),
18608                                }],
18609                            )]
18610                            .into_iter()
18611                            .collect(),
18612                        ),
18613                        ..Default::default()
18614                    }),
18615                    ..Default::default()
18616                },
18617            )]))
18618        },
18619    );
18620    cx.update_editor(|editor, window, cx| {
18621        editor.toggle_code_actions(
18622            &ToggleCodeActions {
18623                deployed_from: None,
18624                quick_launch: false,
18625            },
18626            window,
18627            cx,
18628        );
18629    });
18630    code_action_requests.next().await;
18631    cx.run_until_parked();
18632    cx.condition(|editor, _| editor.context_menu_visible())
18633        .await;
18634    cx.update_editor(|editor, _, _| {
18635        assert!(
18636            !editor.hover_state.visible(),
18637            "Hover popover should be hidden when code action menu is shown"
18638        );
18639        // Hide code actions
18640        editor.context_menu.take();
18641    });
18642
18643    // Case 2: Test that code completions hide hover popover
18644    cx.dispatch_action(Hover);
18645    hover_requests.next().await;
18646    cx.condition(|editor, _| editor.hover_state.visible()).await;
18647    let counter = Arc::new(AtomicUsize::new(0));
18648    let mut completion_requests =
18649        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18650            let counter = counter.clone();
18651            async move {
18652                counter.fetch_add(1, atomic::Ordering::Release);
18653                Ok(Some(lsp::CompletionResponse::Array(vec![
18654                    lsp::CompletionItem {
18655                        label: "main".into(),
18656                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18657                        detail: Some("() -> ()".to_string()),
18658                        ..Default::default()
18659                    },
18660                    lsp::CompletionItem {
18661                        label: "TestStruct".into(),
18662                        kind: Some(lsp::CompletionItemKind::STRUCT),
18663                        detail: Some("struct TestStruct".to_string()),
18664                        ..Default::default()
18665                    },
18666                ])))
18667            }
18668        });
18669    cx.update_editor(|editor, window, cx| {
18670        editor.show_completions(&ShowCompletions, window, cx);
18671    });
18672    completion_requests.next().await;
18673    cx.condition(|editor, _| editor.context_menu_visible())
18674        .await;
18675    cx.update_editor(|editor, _, _| {
18676        assert!(
18677            !editor.hover_state.visible(),
18678            "Hover popover should be hidden when completion menu is shown"
18679        );
18680    });
18681}
18682
18683#[gpui::test]
18684async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18685    init_test(cx, |_| {});
18686
18687    let mut cx = EditorLspTestContext::new_rust(
18688        lsp::ServerCapabilities {
18689            completion_provider: Some(lsp::CompletionOptions {
18690                trigger_characters: Some(vec![".".to_string()]),
18691                resolve_provider: Some(true),
18692                ..Default::default()
18693            }),
18694            ..Default::default()
18695        },
18696        cx,
18697    )
18698    .await;
18699
18700    cx.set_state("fn main() { let a = 2ˇ; }");
18701    cx.simulate_keystroke(".");
18702
18703    let unresolved_item_1 = lsp::CompletionItem {
18704        label: "id".to_string(),
18705        filter_text: Some("id".to_string()),
18706        detail: None,
18707        documentation: None,
18708        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18709            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18710            new_text: ".id".to_string(),
18711        })),
18712        ..lsp::CompletionItem::default()
18713    };
18714    let resolved_item_1 = lsp::CompletionItem {
18715        additional_text_edits: Some(vec![lsp::TextEdit {
18716            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18717            new_text: "!!".to_string(),
18718        }]),
18719        ..unresolved_item_1.clone()
18720    };
18721    let unresolved_item_2 = lsp::CompletionItem {
18722        label: "other".to_string(),
18723        filter_text: Some("other".to_string()),
18724        detail: None,
18725        documentation: None,
18726        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18727            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18728            new_text: ".other".to_string(),
18729        })),
18730        ..lsp::CompletionItem::default()
18731    };
18732    let resolved_item_2 = lsp::CompletionItem {
18733        additional_text_edits: Some(vec![lsp::TextEdit {
18734            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18735            new_text: "??".to_string(),
18736        }]),
18737        ..unresolved_item_2.clone()
18738    };
18739
18740    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18741    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18742    cx.lsp
18743        .server
18744        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18745            let unresolved_item_1 = unresolved_item_1.clone();
18746            let resolved_item_1 = resolved_item_1.clone();
18747            let unresolved_item_2 = unresolved_item_2.clone();
18748            let resolved_item_2 = resolved_item_2.clone();
18749            let resolve_requests_1 = resolve_requests_1.clone();
18750            let resolve_requests_2 = resolve_requests_2.clone();
18751            move |unresolved_request, _| {
18752                let unresolved_item_1 = unresolved_item_1.clone();
18753                let resolved_item_1 = resolved_item_1.clone();
18754                let unresolved_item_2 = unresolved_item_2.clone();
18755                let resolved_item_2 = resolved_item_2.clone();
18756                let resolve_requests_1 = resolve_requests_1.clone();
18757                let resolve_requests_2 = resolve_requests_2.clone();
18758                async move {
18759                    if unresolved_request == unresolved_item_1 {
18760                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18761                        Ok(resolved_item_1.clone())
18762                    } else if unresolved_request == unresolved_item_2 {
18763                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18764                        Ok(resolved_item_2.clone())
18765                    } else {
18766                        panic!("Unexpected completion item {unresolved_request:?}")
18767                    }
18768                }
18769            }
18770        })
18771        .detach();
18772
18773    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18774        let unresolved_item_1 = unresolved_item_1.clone();
18775        let unresolved_item_2 = unresolved_item_2.clone();
18776        async move {
18777            Ok(Some(lsp::CompletionResponse::Array(vec![
18778                unresolved_item_1,
18779                unresolved_item_2,
18780            ])))
18781        }
18782    })
18783    .next()
18784    .await;
18785
18786    cx.condition(|editor, _| editor.context_menu_visible())
18787        .await;
18788    cx.update_editor(|editor, _, _| {
18789        let context_menu = editor.context_menu.borrow_mut();
18790        let context_menu = context_menu
18791            .as_ref()
18792            .expect("Should have the context menu deployed");
18793        match context_menu {
18794            CodeContextMenu::Completions(completions_menu) => {
18795                let completions = completions_menu.completions.borrow_mut();
18796                assert_eq!(
18797                    completions
18798                        .iter()
18799                        .map(|completion| &completion.label.text)
18800                        .collect::<Vec<_>>(),
18801                    vec!["id", "other"]
18802                )
18803            }
18804            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18805        }
18806    });
18807    cx.run_until_parked();
18808
18809    cx.update_editor(|editor, window, cx| {
18810        editor.context_menu_next(&ContextMenuNext, window, cx);
18811    });
18812    cx.run_until_parked();
18813    cx.update_editor(|editor, window, cx| {
18814        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18815    });
18816    cx.run_until_parked();
18817    cx.update_editor(|editor, window, cx| {
18818        editor.context_menu_next(&ContextMenuNext, window, cx);
18819    });
18820    cx.run_until_parked();
18821    cx.update_editor(|editor, window, cx| {
18822        editor
18823            .compose_completion(&ComposeCompletion::default(), window, cx)
18824            .expect("No task returned")
18825    })
18826    .await
18827    .expect("Completion failed");
18828    cx.run_until_parked();
18829
18830    cx.update_editor(|editor, _, cx| {
18831        assert_eq!(
18832            resolve_requests_1.load(atomic::Ordering::Acquire),
18833            1,
18834            "Should always resolve once despite multiple selections"
18835        );
18836        assert_eq!(
18837            resolve_requests_2.load(atomic::Ordering::Acquire),
18838            1,
18839            "Should always resolve once after multiple selections and applying the completion"
18840        );
18841        assert_eq!(
18842            editor.text(cx),
18843            "fn main() { let a = ??.other; }",
18844            "Should use resolved data when applying the completion"
18845        );
18846    });
18847}
18848
18849#[gpui::test]
18850async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18851    init_test(cx, |_| {});
18852
18853    let item_0 = lsp::CompletionItem {
18854        label: "abs".into(),
18855        insert_text: Some("abs".into()),
18856        data: Some(json!({ "very": "special"})),
18857        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18858        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18859            lsp::InsertReplaceEdit {
18860                new_text: "abs".to_string(),
18861                insert: lsp::Range::default(),
18862                replace: lsp::Range::default(),
18863            },
18864        )),
18865        ..lsp::CompletionItem::default()
18866    };
18867    let items = iter::once(item_0.clone())
18868        .chain((11..51).map(|i| lsp::CompletionItem {
18869            label: format!("item_{}", i),
18870            insert_text: Some(format!("item_{}", i)),
18871            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18872            ..lsp::CompletionItem::default()
18873        }))
18874        .collect::<Vec<_>>();
18875
18876    let default_commit_characters = vec!["?".to_string()];
18877    let default_data = json!({ "default": "data"});
18878    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18879    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18880    let default_edit_range = lsp::Range {
18881        start: lsp::Position {
18882            line: 0,
18883            character: 5,
18884        },
18885        end: lsp::Position {
18886            line: 0,
18887            character: 5,
18888        },
18889    };
18890
18891    let mut cx = EditorLspTestContext::new_rust(
18892        lsp::ServerCapabilities {
18893            completion_provider: Some(lsp::CompletionOptions {
18894                trigger_characters: Some(vec![".".to_string()]),
18895                resolve_provider: Some(true),
18896                ..Default::default()
18897            }),
18898            ..Default::default()
18899        },
18900        cx,
18901    )
18902    .await;
18903
18904    cx.set_state("fn main() { let a = 2ˇ; }");
18905    cx.simulate_keystroke(".");
18906
18907    let completion_data = default_data.clone();
18908    let completion_characters = default_commit_characters.clone();
18909    let completion_items = items.clone();
18910    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18911        let default_data = completion_data.clone();
18912        let default_commit_characters = completion_characters.clone();
18913        let items = completion_items.clone();
18914        async move {
18915            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18916                items,
18917                item_defaults: Some(lsp::CompletionListItemDefaults {
18918                    data: Some(default_data.clone()),
18919                    commit_characters: Some(default_commit_characters.clone()),
18920                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18921                        default_edit_range,
18922                    )),
18923                    insert_text_format: Some(default_insert_text_format),
18924                    insert_text_mode: Some(default_insert_text_mode),
18925                }),
18926                ..lsp::CompletionList::default()
18927            })))
18928        }
18929    })
18930    .next()
18931    .await;
18932
18933    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18934    cx.lsp
18935        .server
18936        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18937            let closure_resolved_items = resolved_items.clone();
18938            move |item_to_resolve, _| {
18939                let closure_resolved_items = closure_resolved_items.clone();
18940                async move {
18941                    closure_resolved_items.lock().push(item_to_resolve.clone());
18942                    Ok(item_to_resolve)
18943                }
18944            }
18945        })
18946        .detach();
18947
18948    cx.condition(|editor, _| editor.context_menu_visible())
18949        .await;
18950    cx.run_until_parked();
18951    cx.update_editor(|editor, _, _| {
18952        let menu = editor.context_menu.borrow_mut();
18953        match menu.as_ref().expect("should have the completions menu") {
18954            CodeContextMenu::Completions(completions_menu) => {
18955                assert_eq!(
18956                    completions_menu
18957                        .entries
18958                        .borrow()
18959                        .iter()
18960                        .map(|mat| mat.string.clone())
18961                        .collect::<Vec<String>>(),
18962                    items
18963                        .iter()
18964                        .map(|completion| completion.label.clone())
18965                        .collect::<Vec<String>>()
18966                );
18967            }
18968            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18969        }
18970    });
18971    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18972    // with 4 from the end.
18973    assert_eq!(
18974        *resolved_items.lock(),
18975        [&items[0..16], &items[items.len() - 4..items.len()]]
18976            .concat()
18977            .iter()
18978            .cloned()
18979            .map(|mut item| {
18980                if item.data.is_none() {
18981                    item.data = Some(default_data.clone());
18982                }
18983                item
18984            })
18985            .collect::<Vec<lsp::CompletionItem>>(),
18986        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18987    );
18988    resolved_items.lock().clear();
18989
18990    cx.update_editor(|editor, window, cx| {
18991        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18992    });
18993    cx.run_until_parked();
18994    // Completions that have already been resolved are skipped.
18995    assert_eq!(
18996        *resolved_items.lock(),
18997        items[items.len() - 17..items.len() - 4]
18998            .iter()
18999            .cloned()
19000            .map(|mut item| {
19001                if item.data.is_none() {
19002                    item.data = Some(default_data.clone());
19003                }
19004                item
19005            })
19006            .collect::<Vec<lsp::CompletionItem>>()
19007    );
19008    resolved_items.lock().clear();
19009}
19010
19011#[gpui::test]
19012async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
19013    init_test(cx, |_| {});
19014
19015    let mut cx = EditorLspTestContext::new(
19016        Language::new(
19017            LanguageConfig {
19018                matcher: LanguageMatcher {
19019                    path_suffixes: vec!["jsx".into()],
19020                    ..Default::default()
19021                },
19022                overrides: [(
19023                    "element".into(),
19024                    LanguageConfigOverride {
19025                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
19026                        ..Default::default()
19027                    },
19028                )]
19029                .into_iter()
19030                .collect(),
19031                ..Default::default()
19032            },
19033            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
19034        )
19035        .with_override_query("(jsx_self_closing_element) @element")
19036        .unwrap(),
19037        lsp::ServerCapabilities {
19038            completion_provider: Some(lsp::CompletionOptions {
19039                trigger_characters: Some(vec![":".to_string()]),
19040                ..Default::default()
19041            }),
19042            ..Default::default()
19043        },
19044        cx,
19045    )
19046    .await;
19047
19048    cx.lsp
19049        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
19050            Ok(Some(lsp::CompletionResponse::Array(vec![
19051                lsp::CompletionItem {
19052                    label: "bg-blue".into(),
19053                    ..Default::default()
19054                },
19055                lsp::CompletionItem {
19056                    label: "bg-red".into(),
19057                    ..Default::default()
19058                },
19059                lsp::CompletionItem {
19060                    label: "bg-yellow".into(),
19061                    ..Default::default()
19062                },
19063            ])))
19064        });
19065
19066    cx.set_state(r#"<p class="bgˇ" />"#);
19067
19068    // Trigger completion when typing a dash, because the dash is an extra
19069    // word character in the 'element' scope, which contains the cursor.
19070    cx.simulate_keystroke("-");
19071    cx.executor().run_until_parked();
19072    cx.update_editor(|editor, _, _| {
19073        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19074        {
19075            assert_eq!(
19076                completion_menu_entries(menu),
19077                &["bg-blue", "bg-red", "bg-yellow"]
19078            );
19079        } else {
19080            panic!("expected completion menu to be open");
19081        }
19082    });
19083
19084    cx.simulate_keystroke("l");
19085    cx.executor().run_until_parked();
19086    cx.update_editor(|editor, _, _| {
19087        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19088        {
19089            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
19090        } else {
19091            panic!("expected completion menu to be open");
19092        }
19093    });
19094
19095    // When filtering completions, consider the character after the '-' to
19096    // be the start of a subword.
19097    cx.set_state(r#"<p class="yelˇ" />"#);
19098    cx.simulate_keystroke("l");
19099    cx.executor().run_until_parked();
19100    cx.update_editor(|editor, _, _| {
19101        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
19102        {
19103            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
19104        } else {
19105            panic!("expected completion menu to be open");
19106        }
19107    });
19108}
19109
19110fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19111    let entries = menu.entries.borrow();
19112    entries.iter().map(|mat| mat.string.clone()).collect()
19113}
19114
19115#[gpui::test]
19116async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19117    init_test(cx, |settings| {
19118        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19119    });
19120
19121    let fs = FakeFs::new(cx.executor());
19122    fs.insert_file(path!("/file.ts"), Default::default()).await;
19123
19124    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19125    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19126
19127    language_registry.add(Arc::new(Language::new(
19128        LanguageConfig {
19129            name: "TypeScript".into(),
19130            matcher: LanguageMatcher {
19131                path_suffixes: vec!["ts".to_string()],
19132                ..Default::default()
19133            },
19134            ..Default::default()
19135        },
19136        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19137    )));
19138    update_test_language_settings(cx, |settings| {
19139        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19140    });
19141
19142    let test_plugin = "test_plugin";
19143    let _ = language_registry.register_fake_lsp(
19144        "TypeScript",
19145        FakeLspAdapter {
19146            prettier_plugins: vec![test_plugin],
19147            ..Default::default()
19148        },
19149    );
19150
19151    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19152    let buffer = project
19153        .update(cx, |project, cx| {
19154            project.open_local_buffer(path!("/file.ts"), cx)
19155        })
19156        .await
19157        .unwrap();
19158
19159    let buffer_text = "one\ntwo\nthree\n";
19160    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19161    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19162    editor.update_in(cx, |editor, window, cx| {
19163        editor.set_text(buffer_text, window, cx)
19164    });
19165
19166    editor
19167        .update_in(cx, |editor, window, cx| {
19168            editor.perform_format(
19169                project.clone(),
19170                FormatTrigger::Manual,
19171                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19172                window,
19173                cx,
19174            )
19175        })
19176        .unwrap()
19177        .await;
19178    assert_eq!(
19179        editor.update(cx, |editor, cx| editor.text(cx)),
19180        buffer_text.to_string() + prettier_format_suffix,
19181        "Test prettier formatting was not applied to the original buffer text",
19182    );
19183
19184    update_test_language_settings(cx, |settings| {
19185        settings.defaults.formatter = Some(FormatterList::default())
19186    });
19187    let format = editor.update_in(cx, |editor, window, cx| {
19188        editor.perform_format(
19189            project.clone(),
19190            FormatTrigger::Manual,
19191            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19192            window,
19193            cx,
19194        )
19195    });
19196    format.await.unwrap();
19197    assert_eq!(
19198        editor.update(cx, |editor, cx| editor.text(cx)),
19199        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19200        "Autoformatting (via test prettier) was not applied to the original buffer text",
19201    );
19202}
19203
19204#[gpui::test]
19205async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19206    init_test(cx, |settings| {
19207        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19208    });
19209
19210    let fs = FakeFs::new(cx.executor());
19211    fs.insert_file(path!("/file.settings"), Default::default())
19212        .await;
19213
19214    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19215    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19216
19217    let ts_lang = Arc::new(Language::new(
19218        LanguageConfig {
19219            name: "TypeScript".into(),
19220            matcher: LanguageMatcher {
19221                path_suffixes: vec!["ts".to_string()],
19222                ..LanguageMatcher::default()
19223            },
19224            prettier_parser_name: Some("typescript".to_string()),
19225            ..LanguageConfig::default()
19226        },
19227        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19228    ));
19229
19230    language_registry.add(ts_lang.clone());
19231
19232    update_test_language_settings(cx, |settings| {
19233        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19234    });
19235
19236    let test_plugin = "test_plugin";
19237    let _ = language_registry.register_fake_lsp(
19238        "TypeScript",
19239        FakeLspAdapter {
19240            prettier_plugins: vec![test_plugin],
19241            ..Default::default()
19242        },
19243    );
19244
19245    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19246    let buffer = project
19247        .update(cx, |project, cx| {
19248            project.open_local_buffer(path!("/file.settings"), cx)
19249        })
19250        .await
19251        .unwrap();
19252
19253    project.update(cx, |project, cx| {
19254        project.set_language_for_buffer(&buffer, ts_lang, cx)
19255    });
19256
19257    let buffer_text = "one\ntwo\nthree\n";
19258    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19259    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19260    editor.update_in(cx, |editor, window, cx| {
19261        editor.set_text(buffer_text, window, cx)
19262    });
19263
19264    editor
19265        .update_in(cx, |editor, window, cx| {
19266            editor.perform_format(
19267                project.clone(),
19268                FormatTrigger::Manual,
19269                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19270                window,
19271                cx,
19272            )
19273        })
19274        .unwrap()
19275        .await;
19276    assert_eq!(
19277        editor.update(cx, |editor, cx| editor.text(cx)),
19278        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19279        "Test prettier formatting was not applied to the original buffer text",
19280    );
19281
19282    update_test_language_settings(cx, |settings| {
19283        settings.defaults.formatter = Some(FormatterList::default())
19284    });
19285    let format = editor.update_in(cx, |editor, window, cx| {
19286        editor.perform_format(
19287            project.clone(),
19288            FormatTrigger::Manual,
19289            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19290            window,
19291            cx,
19292        )
19293    });
19294    format.await.unwrap();
19295
19296    assert_eq!(
19297        editor.update(cx, |editor, cx| editor.text(cx)),
19298        buffer_text.to_string()
19299            + prettier_format_suffix
19300            + "\ntypescript\n"
19301            + prettier_format_suffix
19302            + "\ntypescript",
19303        "Autoformatting (via test prettier) was not applied to the original buffer text",
19304    );
19305}
19306
19307#[gpui::test]
19308async fn test_addition_reverts(cx: &mut TestAppContext) {
19309    init_test(cx, |_| {});
19310    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19311    let base_text = indoc! {r#"
19312        struct Row;
19313        struct Row1;
19314        struct Row2;
19315
19316        struct Row4;
19317        struct Row5;
19318        struct Row6;
19319
19320        struct Row8;
19321        struct Row9;
19322        struct Row10;"#};
19323
19324    // When addition hunks are not adjacent to carets, no hunk revert is performed
19325    assert_hunk_revert(
19326        indoc! {r#"struct Row;
19327                   struct Row1;
19328                   struct Row1.1;
19329                   struct Row1.2;
19330                   struct Row2;ˇ
19331
19332                   struct Row4;
19333                   struct Row5;
19334                   struct Row6;
19335
19336                   struct Row8;
19337                   ˇstruct Row9;
19338                   struct Row9.1;
19339                   struct Row9.2;
19340                   struct Row9.3;
19341                   struct Row10;"#},
19342        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19343        indoc! {r#"struct Row;
19344                   struct Row1;
19345                   struct Row1.1;
19346                   struct Row1.2;
19347                   struct Row2;ˇ
19348
19349                   struct Row4;
19350                   struct Row5;
19351                   struct Row6;
19352
19353                   struct Row8;
19354                   ˇstruct Row9;
19355                   struct Row9.1;
19356                   struct Row9.2;
19357                   struct Row9.3;
19358                   struct Row10;"#},
19359        base_text,
19360        &mut cx,
19361    );
19362    // Same for selections
19363    assert_hunk_revert(
19364        indoc! {r#"struct Row;
19365                   struct Row1;
19366                   struct Row2;
19367                   struct Row2.1;
19368                   struct Row2.2;
19369                   «ˇ
19370                   struct Row4;
19371                   struct» Row5;
19372                   «struct Row6;
19373                   ˇ»
19374                   struct Row9.1;
19375                   struct Row9.2;
19376                   struct Row9.3;
19377                   struct Row8;
19378                   struct Row9;
19379                   struct Row10;"#},
19380        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19381        indoc! {r#"struct Row;
19382                   struct Row1;
19383                   struct Row2;
19384                   struct Row2.1;
19385                   struct Row2.2;
19386                   «ˇ
19387                   struct Row4;
19388                   struct» Row5;
19389                   «struct Row6;
19390                   ˇ»
19391                   struct Row9.1;
19392                   struct Row9.2;
19393                   struct Row9.3;
19394                   struct Row8;
19395                   struct Row9;
19396                   struct Row10;"#},
19397        base_text,
19398        &mut cx,
19399    );
19400
19401    // When carets and selections intersect the addition hunks, those are reverted.
19402    // Adjacent carets got merged.
19403    assert_hunk_revert(
19404        indoc! {r#"struct Row;
19405                   ˇ// something on the top
19406                   struct Row1;
19407                   struct Row2;
19408                   struct Roˇw3.1;
19409                   struct Row2.2;
19410                   struct Row2.3;ˇ
19411
19412                   struct Row4;
19413                   struct ˇRow5.1;
19414                   struct Row5.2;
19415                   struct «Rowˇ»5.3;
19416                   struct Row5;
19417                   struct Row6;
19418                   ˇ
19419                   struct Row9.1;
19420                   struct «Rowˇ»9.2;
19421                   struct «ˇRow»9.3;
19422                   struct Row8;
19423                   struct Row9;
19424                   «ˇ// something on bottom»
19425                   struct Row10;"#},
19426        vec![
19427            DiffHunkStatusKind::Added,
19428            DiffHunkStatusKind::Added,
19429            DiffHunkStatusKind::Added,
19430            DiffHunkStatusKind::Added,
19431            DiffHunkStatusKind::Added,
19432        ],
19433        indoc! {r#"struct Row;
19434                   ˇstruct Row1;
19435                   struct Row2;
19436                   ˇ
19437                   struct Row4;
19438                   ˇstruct Row5;
19439                   struct Row6;
19440                   ˇ
19441                   ˇstruct Row8;
19442                   struct Row9;
19443                   ˇstruct Row10;"#},
19444        base_text,
19445        &mut cx,
19446    );
19447}
19448
19449#[gpui::test]
19450async fn test_modification_reverts(cx: &mut TestAppContext) {
19451    init_test(cx, |_| {});
19452    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19453    let base_text = indoc! {r#"
19454        struct Row;
19455        struct Row1;
19456        struct Row2;
19457
19458        struct Row4;
19459        struct Row5;
19460        struct Row6;
19461
19462        struct Row8;
19463        struct Row9;
19464        struct Row10;"#};
19465
19466    // Modification hunks behave the same as the addition ones.
19467    assert_hunk_revert(
19468        indoc! {r#"struct Row;
19469                   struct Row1;
19470                   struct Row33;
19471                   ˇ
19472                   struct Row4;
19473                   struct Row5;
19474                   struct Row6;
19475                   ˇ
19476                   struct Row99;
19477                   struct Row9;
19478                   struct Row10;"#},
19479        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19480        indoc! {r#"struct Row;
19481                   struct Row1;
19482                   struct Row33;
19483                   ˇ
19484                   struct Row4;
19485                   struct Row5;
19486                   struct Row6;
19487                   ˇ
19488                   struct Row99;
19489                   struct Row9;
19490                   struct Row10;"#},
19491        base_text,
19492        &mut cx,
19493    );
19494    assert_hunk_revert(
19495        indoc! {r#"struct Row;
19496                   struct Row1;
19497                   struct Row33;
19498                   «ˇ
19499                   struct Row4;
19500                   struct» Row5;
19501                   «struct Row6;
19502                   ˇ»
19503                   struct Row99;
19504                   struct Row9;
19505                   struct Row10;"#},
19506        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19507        indoc! {r#"struct Row;
19508                   struct Row1;
19509                   struct Row33;
19510                   «ˇ
19511                   struct Row4;
19512                   struct» Row5;
19513                   «struct Row6;
19514                   ˇ»
19515                   struct Row99;
19516                   struct Row9;
19517                   struct Row10;"#},
19518        base_text,
19519        &mut cx,
19520    );
19521
19522    assert_hunk_revert(
19523        indoc! {r#"ˇstruct Row1.1;
19524                   struct Row1;
19525                   «ˇstr»uct Row22;
19526
19527                   struct ˇRow44;
19528                   struct Row5;
19529                   struct «Rˇ»ow66;ˇ
19530
19531                   «struˇ»ct Row88;
19532                   struct Row9;
19533                   struct Row1011;ˇ"#},
19534        vec![
19535            DiffHunkStatusKind::Modified,
19536            DiffHunkStatusKind::Modified,
19537            DiffHunkStatusKind::Modified,
19538            DiffHunkStatusKind::Modified,
19539            DiffHunkStatusKind::Modified,
19540            DiffHunkStatusKind::Modified,
19541        ],
19542        indoc! {r#"struct Row;
19543                   ˇstruct Row1;
19544                   struct Row2;
19545                   ˇ
19546                   struct Row4;
19547                   ˇstruct Row5;
19548                   struct Row6;
19549                   ˇ
19550                   struct Row8;
19551                   ˇstruct Row9;
19552                   struct Row10;ˇ"#},
19553        base_text,
19554        &mut cx,
19555    );
19556}
19557
19558#[gpui::test]
19559async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19560    init_test(cx, |_| {});
19561    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19562    let base_text = indoc! {r#"
19563        one
19564
19565        two
19566        three
19567        "#};
19568
19569    cx.set_head_text(base_text);
19570    cx.set_state("\nˇ\n");
19571    cx.executor().run_until_parked();
19572    cx.update_editor(|editor, _window, cx| {
19573        editor.expand_selected_diff_hunks(cx);
19574    });
19575    cx.executor().run_until_parked();
19576    cx.update_editor(|editor, window, cx| {
19577        editor.backspace(&Default::default(), window, cx);
19578    });
19579    cx.run_until_parked();
19580    cx.assert_state_with_diff(
19581        indoc! {r#"
19582
19583        - two
19584        - threeˇ
19585        +
19586        "#}
19587        .to_string(),
19588    );
19589}
19590
19591#[gpui::test]
19592async fn test_deletion_reverts(cx: &mut TestAppContext) {
19593    init_test(cx, |_| {});
19594    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19595    let base_text = indoc! {r#"struct Row;
19596struct Row1;
19597struct Row2;
19598
19599struct Row4;
19600struct Row5;
19601struct Row6;
19602
19603struct Row8;
19604struct Row9;
19605struct Row10;"#};
19606
19607    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19608    assert_hunk_revert(
19609        indoc! {r#"struct Row;
19610                   struct Row2;
19611
19612                   ˇstruct Row4;
19613                   struct Row5;
19614                   struct Row6;
19615                   ˇ
19616                   struct Row8;
19617                   struct Row10;"#},
19618        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19619        indoc! {r#"struct Row;
19620                   struct Row2;
19621
19622                   ˇstruct Row4;
19623                   struct Row5;
19624                   struct Row6;
19625                   ˇ
19626                   struct Row8;
19627                   struct Row10;"#},
19628        base_text,
19629        &mut cx,
19630    );
19631    assert_hunk_revert(
19632        indoc! {r#"struct Row;
19633                   struct Row2;
19634
19635                   «ˇstruct Row4;
19636                   struct» Row5;
19637                   «struct Row6;
19638                   ˇ»
19639                   struct Row8;
19640                   struct Row10;"#},
19641        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19642        indoc! {r#"struct Row;
19643                   struct Row2;
19644
19645                   «ˇstruct Row4;
19646                   struct» Row5;
19647                   «struct Row6;
19648                   ˇ»
19649                   struct Row8;
19650                   struct Row10;"#},
19651        base_text,
19652        &mut cx,
19653    );
19654
19655    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19656    assert_hunk_revert(
19657        indoc! {r#"struct Row;
19658                   ˇstruct Row2;
19659
19660                   struct Row4;
19661                   struct Row5;
19662                   struct Row6;
19663
19664                   struct Row8;ˇ
19665                   struct Row10;"#},
19666        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19667        indoc! {r#"struct Row;
19668                   struct Row1;
19669                   ˇstruct Row2;
19670
19671                   struct Row4;
19672                   struct Row5;
19673                   struct Row6;
19674
19675                   struct Row8;ˇ
19676                   struct Row9;
19677                   struct Row10;"#},
19678        base_text,
19679        &mut cx,
19680    );
19681    assert_hunk_revert(
19682        indoc! {r#"struct Row;
19683                   struct Row2«ˇ;
19684                   struct Row4;
19685                   struct» Row5;
19686                   «struct Row6;
19687
19688                   struct Row8;ˇ»
19689                   struct Row10;"#},
19690        vec![
19691            DiffHunkStatusKind::Deleted,
19692            DiffHunkStatusKind::Deleted,
19693            DiffHunkStatusKind::Deleted,
19694        ],
19695        indoc! {r#"struct Row;
19696                   struct Row1;
19697                   struct Row2«ˇ;
19698
19699                   struct Row4;
19700                   struct» Row5;
19701                   «struct Row6;
19702
19703                   struct Row8;ˇ»
19704                   struct Row9;
19705                   struct Row10;"#},
19706        base_text,
19707        &mut cx,
19708    );
19709}
19710
19711#[gpui::test]
19712async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19713    init_test(cx, |_| {});
19714
19715    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19716    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19717    let base_text_3 =
19718        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19719
19720    let text_1 = edit_first_char_of_every_line(base_text_1);
19721    let text_2 = edit_first_char_of_every_line(base_text_2);
19722    let text_3 = edit_first_char_of_every_line(base_text_3);
19723
19724    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19725    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19726    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19727
19728    let multibuffer = cx.new(|cx| {
19729        let mut multibuffer = MultiBuffer::new(ReadWrite);
19730        multibuffer.push_excerpts(
19731            buffer_1.clone(),
19732            [
19733                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19734                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19735                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19736            ],
19737            cx,
19738        );
19739        multibuffer.push_excerpts(
19740            buffer_2.clone(),
19741            [
19742                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19743                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19744                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19745            ],
19746            cx,
19747        );
19748        multibuffer.push_excerpts(
19749            buffer_3.clone(),
19750            [
19751                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19752                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19753                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19754            ],
19755            cx,
19756        );
19757        multibuffer
19758    });
19759
19760    let fs = FakeFs::new(cx.executor());
19761    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19762    let (editor, cx) = cx
19763        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19764    editor.update_in(cx, |editor, _window, cx| {
19765        for (buffer, diff_base) in [
19766            (buffer_1.clone(), base_text_1),
19767            (buffer_2.clone(), base_text_2),
19768            (buffer_3.clone(), base_text_3),
19769        ] {
19770            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19771            editor
19772                .buffer
19773                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19774        }
19775    });
19776    cx.executor().run_until_parked();
19777
19778    editor.update_in(cx, |editor, window, cx| {
19779        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}");
19780        editor.select_all(&SelectAll, window, cx);
19781        editor.git_restore(&Default::default(), window, cx);
19782    });
19783    cx.executor().run_until_parked();
19784
19785    // When all ranges are selected, all buffer hunks are reverted.
19786    editor.update(cx, |editor, cx| {
19787        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");
19788    });
19789    buffer_1.update(cx, |buffer, _| {
19790        assert_eq!(buffer.text(), base_text_1);
19791    });
19792    buffer_2.update(cx, |buffer, _| {
19793        assert_eq!(buffer.text(), base_text_2);
19794    });
19795    buffer_3.update(cx, |buffer, _| {
19796        assert_eq!(buffer.text(), base_text_3);
19797    });
19798
19799    editor.update_in(cx, |editor, window, cx| {
19800        editor.undo(&Default::default(), window, cx);
19801    });
19802
19803    editor.update_in(cx, |editor, window, cx| {
19804        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19805            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19806        });
19807        editor.git_restore(&Default::default(), window, cx);
19808    });
19809
19810    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19811    // but not affect buffer_2 and its related excerpts.
19812    editor.update(cx, |editor, cx| {
19813        assert_eq!(
19814            editor.text(cx),
19815            "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}"
19816        );
19817    });
19818    buffer_1.update(cx, |buffer, _| {
19819        assert_eq!(buffer.text(), base_text_1);
19820    });
19821    buffer_2.update(cx, |buffer, _| {
19822        assert_eq!(
19823            buffer.text(),
19824            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19825        );
19826    });
19827    buffer_3.update(cx, |buffer, _| {
19828        assert_eq!(
19829            buffer.text(),
19830            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19831        );
19832    });
19833
19834    fn edit_first_char_of_every_line(text: &str) -> String {
19835        text.split('\n')
19836            .map(|line| format!("X{}", &line[1..]))
19837            .collect::<Vec<_>>()
19838            .join("\n")
19839    }
19840}
19841
19842#[gpui::test]
19843async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19844    init_test(cx, |_| {});
19845
19846    let cols = 4;
19847    let rows = 10;
19848    let sample_text_1 = sample_text(rows, cols, 'a');
19849    assert_eq!(
19850        sample_text_1,
19851        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19852    );
19853    let sample_text_2 = sample_text(rows, cols, 'l');
19854    assert_eq!(
19855        sample_text_2,
19856        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19857    );
19858    let sample_text_3 = sample_text(rows, cols, 'v');
19859    assert_eq!(
19860        sample_text_3,
19861        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19862    );
19863
19864    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19865    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19866    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19867
19868    let multi_buffer = cx.new(|cx| {
19869        let mut multibuffer = MultiBuffer::new(ReadWrite);
19870        multibuffer.push_excerpts(
19871            buffer_1.clone(),
19872            [
19873                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19874                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19875                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19876            ],
19877            cx,
19878        );
19879        multibuffer.push_excerpts(
19880            buffer_2.clone(),
19881            [
19882                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19883                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19884                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19885            ],
19886            cx,
19887        );
19888        multibuffer.push_excerpts(
19889            buffer_3.clone(),
19890            [
19891                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19892                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19893                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19894            ],
19895            cx,
19896        );
19897        multibuffer
19898    });
19899
19900    let fs = FakeFs::new(cx.executor());
19901    fs.insert_tree(
19902        "/a",
19903        json!({
19904            "main.rs": sample_text_1,
19905            "other.rs": sample_text_2,
19906            "lib.rs": sample_text_3,
19907        }),
19908    )
19909    .await;
19910    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19911    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19912    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19913    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19914        Editor::new(
19915            EditorMode::full(),
19916            multi_buffer,
19917            Some(project.clone()),
19918            window,
19919            cx,
19920        )
19921    });
19922    let multibuffer_item_id = workspace
19923        .update(cx, |workspace, window, cx| {
19924            assert!(
19925                workspace.active_item(cx).is_none(),
19926                "active item should be None before the first item is added"
19927            );
19928            workspace.add_item_to_active_pane(
19929                Box::new(multi_buffer_editor.clone()),
19930                None,
19931                true,
19932                window,
19933                cx,
19934            );
19935            let active_item = workspace
19936                .active_item(cx)
19937                .expect("should have an active item after adding the multi buffer");
19938            assert_eq!(
19939                active_item.buffer_kind(cx),
19940                ItemBufferKind::Multibuffer,
19941                "A multi buffer was expected to active after adding"
19942            );
19943            active_item.item_id()
19944        })
19945        .unwrap();
19946    cx.executor().run_until_parked();
19947
19948    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19949        editor.change_selections(
19950            SelectionEffects::scroll(Autoscroll::Next),
19951            window,
19952            cx,
19953            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
19954        );
19955        editor.open_excerpts(&OpenExcerpts, window, cx);
19956    });
19957    cx.executor().run_until_parked();
19958    let first_item_id = workspace
19959        .update(cx, |workspace, window, cx| {
19960            let active_item = workspace
19961                .active_item(cx)
19962                .expect("should have an active item after navigating into the 1st buffer");
19963            let first_item_id = active_item.item_id();
19964            assert_ne!(
19965                first_item_id, multibuffer_item_id,
19966                "Should navigate into the 1st buffer and activate it"
19967            );
19968            assert_eq!(
19969                active_item.buffer_kind(cx),
19970                ItemBufferKind::Singleton,
19971                "New active item should be a singleton buffer"
19972            );
19973            assert_eq!(
19974                active_item
19975                    .act_as::<Editor>(cx)
19976                    .expect("should have navigated into an editor for the 1st buffer")
19977                    .read(cx)
19978                    .text(cx),
19979                sample_text_1
19980            );
19981
19982            workspace
19983                .go_back(workspace.active_pane().downgrade(), window, cx)
19984                .detach_and_log_err(cx);
19985
19986            first_item_id
19987        })
19988        .unwrap();
19989    cx.executor().run_until_parked();
19990    workspace
19991        .update(cx, |workspace, _, cx| {
19992            let active_item = workspace
19993                .active_item(cx)
19994                .expect("should have an active item after navigating back");
19995            assert_eq!(
19996                active_item.item_id(),
19997                multibuffer_item_id,
19998                "Should navigate back to the multi buffer"
19999            );
20000            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20001        })
20002        .unwrap();
20003
20004    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20005        editor.change_selections(
20006            SelectionEffects::scroll(Autoscroll::Next),
20007            window,
20008            cx,
20009            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
20010        );
20011        editor.open_excerpts(&OpenExcerpts, window, cx);
20012    });
20013    cx.executor().run_until_parked();
20014    let second_item_id = workspace
20015        .update(cx, |workspace, window, cx| {
20016            let active_item = workspace
20017                .active_item(cx)
20018                .expect("should have an active item after navigating into the 2nd buffer");
20019            let second_item_id = active_item.item_id();
20020            assert_ne!(
20021                second_item_id, multibuffer_item_id,
20022                "Should navigate away from the multibuffer"
20023            );
20024            assert_ne!(
20025                second_item_id, first_item_id,
20026                "Should navigate into the 2nd buffer and activate it"
20027            );
20028            assert_eq!(
20029                active_item.buffer_kind(cx),
20030                ItemBufferKind::Singleton,
20031                "New active item should be a singleton buffer"
20032            );
20033            assert_eq!(
20034                active_item
20035                    .act_as::<Editor>(cx)
20036                    .expect("should have navigated into an editor")
20037                    .read(cx)
20038                    .text(cx),
20039                sample_text_2
20040            );
20041
20042            workspace
20043                .go_back(workspace.active_pane().downgrade(), window, cx)
20044                .detach_and_log_err(cx);
20045
20046            second_item_id
20047        })
20048        .unwrap();
20049    cx.executor().run_until_parked();
20050    workspace
20051        .update(cx, |workspace, _, cx| {
20052            let active_item = workspace
20053                .active_item(cx)
20054                .expect("should have an active item after navigating back from the 2nd buffer");
20055            assert_eq!(
20056                active_item.item_id(),
20057                multibuffer_item_id,
20058                "Should navigate back from the 2nd buffer to the multi buffer"
20059            );
20060            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20061        })
20062        .unwrap();
20063
20064    multi_buffer_editor.update_in(cx, |editor, window, cx| {
20065        editor.change_selections(
20066            SelectionEffects::scroll(Autoscroll::Next),
20067            window,
20068            cx,
20069            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
20070        );
20071        editor.open_excerpts(&OpenExcerpts, window, cx);
20072    });
20073    cx.executor().run_until_parked();
20074    workspace
20075        .update(cx, |workspace, window, cx| {
20076            let active_item = workspace
20077                .active_item(cx)
20078                .expect("should have an active item after navigating into the 3rd buffer");
20079            let third_item_id = active_item.item_id();
20080            assert_ne!(
20081                third_item_id, multibuffer_item_id,
20082                "Should navigate into the 3rd buffer and activate it"
20083            );
20084            assert_ne!(third_item_id, first_item_id);
20085            assert_ne!(third_item_id, second_item_id);
20086            assert_eq!(
20087                active_item.buffer_kind(cx),
20088                ItemBufferKind::Singleton,
20089                "New active item should be a singleton buffer"
20090            );
20091            assert_eq!(
20092                active_item
20093                    .act_as::<Editor>(cx)
20094                    .expect("should have navigated into an editor")
20095                    .read(cx)
20096                    .text(cx),
20097                sample_text_3
20098            );
20099
20100            workspace
20101                .go_back(workspace.active_pane().downgrade(), window, cx)
20102                .detach_and_log_err(cx);
20103        })
20104        .unwrap();
20105    cx.executor().run_until_parked();
20106    workspace
20107        .update(cx, |workspace, _, cx| {
20108            let active_item = workspace
20109                .active_item(cx)
20110                .expect("should have an active item after navigating back from the 3rd buffer");
20111            assert_eq!(
20112                active_item.item_id(),
20113                multibuffer_item_id,
20114                "Should navigate back from the 3rd buffer to the multi buffer"
20115            );
20116            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20117        })
20118        .unwrap();
20119}
20120
20121#[gpui::test]
20122async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20123    init_test(cx, |_| {});
20124
20125    let mut cx = EditorTestContext::new(cx).await;
20126
20127    let diff_base = r#"
20128        use some::mod;
20129
20130        const A: u32 = 42;
20131
20132        fn main() {
20133            println!("hello");
20134
20135            println!("world");
20136        }
20137        "#
20138    .unindent();
20139
20140    cx.set_state(
20141        &r#"
20142        use some::modified;
20143
20144        ˇ
20145        fn main() {
20146            println!("hello there");
20147
20148            println!("around the");
20149            println!("world");
20150        }
20151        "#
20152        .unindent(),
20153    );
20154
20155    cx.set_head_text(&diff_base);
20156    executor.run_until_parked();
20157
20158    cx.update_editor(|editor, window, cx| {
20159        editor.go_to_next_hunk(&GoToHunk, window, cx);
20160        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20161    });
20162    executor.run_until_parked();
20163    cx.assert_state_with_diff(
20164        r#"
20165          use some::modified;
20166
20167
20168          fn main() {
20169        -     println!("hello");
20170        + ˇ    println!("hello there");
20171
20172              println!("around the");
20173              println!("world");
20174          }
20175        "#
20176        .unindent(),
20177    );
20178
20179    cx.update_editor(|editor, window, cx| {
20180        for _ in 0..2 {
20181            editor.go_to_next_hunk(&GoToHunk, window, cx);
20182            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20183        }
20184    });
20185    executor.run_until_parked();
20186    cx.assert_state_with_diff(
20187        r#"
20188        - use some::mod;
20189        + ˇuse some::modified;
20190
20191
20192          fn main() {
20193        -     println!("hello");
20194        +     println!("hello there");
20195
20196        +     println!("around the");
20197              println!("world");
20198          }
20199        "#
20200        .unindent(),
20201    );
20202
20203    cx.update_editor(|editor, window, cx| {
20204        editor.go_to_next_hunk(&GoToHunk, window, cx);
20205        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20206    });
20207    executor.run_until_parked();
20208    cx.assert_state_with_diff(
20209        r#"
20210        - use some::mod;
20211        + use some::modified;
20212
20213        - const A: u32 = 42;
20214          ˇ
20215          fn main() {
20216        -     println!("hello");
20217        +     println!("hello there");
20218
20219        +     println!("around the");
20220              println!("world");
20221          }
20222        "#
20223        .unindent(),
20224    );
20225
20226    cx.update_editor(|editor, window, cx| {
20227        editor.cancel(&Cancel, window, cx);
20228    });
20229
20230    cx.assert_state_with_diff(
20231        r#"
20232          use some::modified;
20233
20234          ˇ
20235          fn main() {
20236              println!("hello there");
20237
20238              println!("around the");
20239              println!("world");
20240          }
20241        "#
20242        .unindent(),
20243    );
20244}
20245
20246#[gpui::test]
20247async fn test_diff_base_change_with_expanded_diff_hunks(
20248    executor: BackgroundExecutor,
20249    cx: &mut TestAppContext,
20250) {
20251    init_test(cx, |_| {});
20252
20253    let mut cx = EditorTestContext::new(cx).await;
20254
20255    let diff_base = r#"
20256        use some::mod1;
20257        use some::mod2;
20258
20259        const A: u32 = 42;
20260        const B: u32 = 42;
20261        const C: u32 = 42;
20262
20263        fn main() {
20264            println!("hello");
20265
20266            println!("world");
20267        }
20268        "#
20269    .unindent();
20270
20271    cx.set_state(
20272        &r#"
20273        use some::mod2;
20274
20275        const A: u32 = 42;
20276        const C: u32 = 42;
20277
20278        fn main(ˇ) {
20279            //println!("hello");
20280
20281            println!("world");
20282            //
20283            //
20284        }
20285        "#
20286        .unindent(),
20287    );
20288
20289    cx.set_head_text(&diff_base);
20290    executor.run_until_parked();
20291
20292    cx.update_editor(|editor, window, cx| {
20293        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20294    });
20295    executor.run_until_parked();
20296    cx.assert_state_with_diff(
20297        r#"
20298        - use some::mod1;
20299          use some::mod2;
20300
20301          const A: u32 = 42;
20302        - const B: u32 = 42;
20303          const C: u32 = 42;
20304
20305          fn main(ˇ) {
20306        -     println!("hello");
20307        +     //println!("hello");
20308
20309              println!("world");
20310        +     //
20311        +     //
20312          }
20313        "#
20314        .unindent(),
20315    );
20316
20317    cx.set_head_text("new diff base!");
20318    executor.run_until_parked();
20319    cx.assert_state_with_diff(
20320        r#"
20321        - new diff base!
20322        + use some::mod2;
20323        +
20324        + const A: u32 = 42;
20325        + const C: u32 = 42;
20326        +
20327        + fn main(ˇ) {
20328        +     //println!("hello");
20329        +
20330        +     println!("world");
20331        +     //
20332        +     //
20333        + }
20334        "#
20335        .unindent(),
20336    );
20337}
20338
20339#[gpui::test]
20340async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20341    init_test(cx, |_| {});
20342
20343    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20344    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20345    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20346    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20347    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20348    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20349
20350    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20351    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20352    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20353
20354    let multi_buffer = cx.new(|cx| {
20355        let mut multibuffer = MultiBuffer::new(ReadWrite);
20356        multibuffer.push_excerpts(
20357            buffer_1.clone(),
20358            [
20359                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20360                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20361                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20362            ],
20363            cx,
20364        );
20365        multibuffer.push_excerpts(
20366            buffer_2.clone(),
20367            [
20368                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20369                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20370                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20371            ],
20372            cx,
20373        );
20374        multibuffer.push_excerpts(
20375            buffer_3.clone(),
20376            [
20377                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20378                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20379                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20380            ],
20381            cx,
20382        );
20383        multibuffer
20384    });
20385
20386    let editor =
20387        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20388    editor
20389        .update(cx, |editor, _window, cx| {
20390            for (buffer, diff_base) in [
20391                (buffer_1.clone(), file_1_old),
20392                (buffer_2.clone(), file_2_old),
20393                (buffer_3.clone(), file_3_old),
20394            ] {
20395                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20396                editor
20397                    .buffer
20398                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20399            }
20400        })
20401        .unwrap();
20402
20403    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20404    cx.run_until_parked();
20405
20406    cx.assert_editor_state(
20407        &"
20408            ˇaaa
20409            ccc
20410            ddd
20411
20412            ggg
20413            hhh
20414
20415
20416            lll
20417            mmm
20418            NNN
20419
20420            qqq
20421            rrr
20422
20423            uuu
20424            111
20425            222
20426            333
20427
20428            666
20429            777
20430
20431            000
20432            !!!"
20433        .unindent(),
20434    );
20435
20436    cx.update_editor(|editor, window, cx| {
20437        editor.select_all(&SelectAll, window, cx);
20438        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20439    });
20440    cx.executor().run_until_parked();
20441
20442    cx.assert_state_with_diff(
20443        "
20444            «aaa
20445          - bbb
20446            ccc
20447            ddd
20448
20449            ggg
20450            hhh
20451
20452
20453            lll
20454            mmm
20455          - nnn
20456          + NNN
20457
20458            qqq
20459            rrr
20460
20461            uuu
20462            111
20463            222
20464            333
20465
20466          + 666
20467            777
20468
20469            000
20470            !!!ˇ»"
20471            .unindent(),
20472    );
20473}
20474
20475#[gpui::test]
20476async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20477    init_test(cx, |_| {});
20478
20479    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20480    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20481
20482    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20483    let multi_buffer = cx.new(|cx| {
20484        let mut multibuffer = MultiBuffer::new(ReadWrite);
20485        multibuffer.push_excerpts(
20486            buffer.clone(),
20487            [
20488                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20489                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20490                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20491            ],
20492            cx,
20493        );
20494        multibuffer
20495    });
20496
20497    let editor =
20498        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20499    editor
20500        .update(cx, |editor, _window, cx| {
20501            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20502            editor
20503                .buffer
20504                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20505        })
20506        .unwrap();
20507
20508    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20509    cx.run_until_parked();
20510
20511    cx.update_editor(|editor, window, cx| {
20512        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20513    });
20514    cx.executor().run_until_parked();
20515
20516    // When the start of a hunk coincides with the start of its excerpt,
20517    // the hunk is expanded. When the start of a hunk is earlier than
20518    // the start of its excerpt, the hunk is not expanded.
20519    cx.assert_state_with_diff(
20520        "
20521            ˇaaa
20522          - bbb
20523          + BBB
20524
20525          - ddd
20526          - eee
20527          + DDD
20528          + EEE
20529            fff
20530
20531            iii
20532        "
20533        .unindent(),
20534    );
20535}
20536
20537#[gpui::test]
20538async fn test_edits_around_expanded_insertion_hunks(
20539    executor: BackgroundExecutor,
20540    cx: &mut TestAppContext,
20541) {
20542    init_test(cx, |_| {});
20543
20544    let mut cx = EditorTestContext::new(cx).await;
20545
20546    let diff_base = r#"
20547        use some::mod1;
20548        use some::mod2;
20549
20550        const A: u32 = 42;
20551
20552        fn main() {
20553            println!("hello");
20554
20555            println!("world");
20556        }
20557        "#
20558    .unindent();
20559    executor.run_until_parked();
20560    cx.set_state(
20561        &r#"
20562        use some::mod1;
20563        use some::mod2;
20564
20565        const A: u32 = 42;
20566        const B: u32 = 42;
20567        const C: u32 = 42;
20568        ˇ
20569
20570        fn main() {
20571            println!("hello");
20572
20573            println!("world");
20574        }
20575        "#
20576        .unindent(),
20577    );
20578
20579    cx.set_head_text(&diff_base);
20580    executor.run_until_parked();
20581
20582    cx.update_editor(|editor, window, cx| {
20583        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20584    });
20585    executor.run_until_parked();
20586
20587    cx.assert_state_with_diff(
20588        r#"
20589        use some::mod1;
20590        use some::mod2;
20591
20592        const A: u32 = 42;
20593      + const B: u32 = 42;
20594      + const C: u32 = 42;
20595      + ˇ
20596
20597        fn main() {
20598            println!("hello");
20599
20600            println!("world");
20601        }
20602      "#
20603        .unindent(),
20604    );
20605
20606    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20607    executor.run_until_parked();
20608
20609    cx.assert_state_with_diff(
20610        r#"
20611        use some::mod1;
20612        use some::mod2;
20613
20614        const A: u32 = 42;
20615      + const B: u32 = 42;
20616      + const C: u32 = 42;
20617      + const D: u32 = 42;
20618      + ˇ
20619
20620        fn main() {
20621            println!("hello");
20622
20623            println!("world");
20624        }
20625      "#
20626        .unindent(),
20627    );
20628
20629    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20630    executor.run_until_parked();
20631
20632    cx.assert_state_with_diff(
20633        r#"
20634        use some::mod1;
20635        use some::mod2;
20636
20637        const A: u32 = 42;
20638      + const B: u32 = 42;
20639      + const C: u32 = 42;
20640      + const D: u32 = 42;
20641      + const E: u32 = 42;
20642      + ˇ
20643
20644        fn main() {
20645            println!("hello");
20646
20647            println!("world");
20648        }
20649      "#
20650        .unindent(),
20651    );
20652
20653    cx.update_editor(|editor, window, cx| {
20654        editor.delete_line(&DeleteLine, window, cx);
20655    });
20656    executor.run_until_parked();
20657
20658    cx.assert_state_with_diff(
20659        r#"
20660        use some::mod1;
20661        use some::mod2;
20662
20663        const A: u32 = 42;
20664      + const B: u32 = 42;
20665      + const C: u32 = 42;
20666      + const D: u32 = 42;
20667      + const E: u32 = 42;
20668        ˇ
20669        fn main() {
20670            println!("hello");
20671
20672            println!("world");
20673        }
20674      "#
20675        .unindent(),
20676    );
20677
20678    cx.update_editor(|editor, window, cx| {
20679        editor.move_up(&MoveUp, window, cx);
20680        editor.delete_line(&DeleteLine, window, cx);
20681        editor.move_up(&MoveUp, window, cx);
20682        editor.delete_line(&DeleteLine, window, cx);
20683        editor.move_up(&MoveUp, window, cx);
20684        editor.delete_line(&DeleteLine, window, cx);
20685    });
20686    executor.run_until_parked();
20687    cx.assert_state_with_diff(
20688        r#"
20689        use some::mod1;
20690        use some::mod2;
20691
20692        const A: u32 = 42;
20693      + const B: u32 = 42;
20694        ˇ
20695        fn main() {
20696            println!("hello");
20697
20698            println!("world");
20699        }
20700      "#
20701        .unindent(),
20702    );
20703
20704    cx.update_editor(|editor, window, cx| {
20705        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20706        editor.delete_line(&DeleteLine, window, cx);
20707    });
20708    executor.run_until_parked();
20709    cx.assert_state_with_diff(
20710        r#"
20711        ˇ
20712        fn main() {
20713            println!("hello");
20714
20715            println!("world");
20716        }
20717      "#
20718        .unindent(),
20719    );
20720}
20721
20722#[gpui::test]
20723async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20724    init_test(cx, |_| {});
20725
20726    let mut cx = EditorTestContext::new(cx).await;
20727    cx.set_head_text(indoc! { "
20728        one
20729        two
20730        three
20731        four
20732        five
20733        "
20734    });
20735    cx.set_state(indoc! { "
20736        one
20737        ˇthree
20738        five
20739    "});
20740    cx.run_until_parked();
20741    cx.update_editor(|editor, window, cx| {
20742        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20743    });
20744    cx.assert_state_with_diff(
20745        indoc! { "
20746        one
20747      - two
20748        ˇthree
20749      - four
20750        five
20751    "}
20752        .to_string(),
20753    );
20754    cx.update_editor(|editor, window, cx| {
20755        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20756    });
20757
20758    cx.assert_state_with_diff(
20759        indoc! { "
20760        one
20761        ˇthree
20762        five
20763    "}
20764        .to_string(),
20765    );
20766
20767    cx.set_state(indoc! { "
20768        one
20769        ˇTWO
20770        three
20771        four
20772        five
20773    "});
20774    cx.run_until_parked();
20775    cx.update_editor(|editor, window, cx| {
20776        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20777    });
20778
20779    cx.assert_state_with_diff(
20780        indoc! { "
20781            one
20782          - two
20783          + ˇTWO
20784            three
20785            four
20786            five
20787        "}
20788        .to_string(),
20789    );
20790    cx.update_editor(|editor, window, cx| {
20791        editor.move_up(&Default::default(), window, cx);
20792        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20793    });
20794    cx.assert_state_with_diff(
20795        indoc! { "
20796            one
20797            ˇTWO
20798            three
20799            four
20800            five
20801        "}
20802        .to_string(),
20803    );
20804}
20805
20806#[gpui::test]
20807async fn test_edits_around_expanded_deletion_hunks(
20808    executor: BackgroundExecutor,
20809    cx: &mut TestAppContext,
20810) {
20811    init_test(cx, |_| {});
20812
20813    let mut cx = EditorTestContext::new(cx).await;
20814
20815    let diff_base = r#"
20816        use some::mod1;
20817        use some::mod2;
20818
20819        const A: u32 = 42;
20820        const B: u32 = 42;
20821        const C: u32 = 42;
20822
20823
20824        fn main() {
20825            println!("hello");
20826
20827            println!("world");
20828        }
20829    "#
20830    .unindent();
20831    executor.run_until_parked();
20832    cx.set_state(
20833        &r#"
20834        use some::mod1;
20835        use some::mod2;
20836
20837        ˇconst B: u32 = 42;
20838        const C: u32 = 42;
20839
20840
20841        fn main() {
20842            println!("hello");
20843
20844            println!("world");
20845        }
20846        "#
20847        .unindent(),
20848    );
20849
20850    cx.set_head_text(&diff_base);
20851    executor.run_until_parked();
20852
20853    cx.update_editor(|editor, window, cx| {
20854        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20855    });
20856    executor.run_until_parked();
20857
20858    cx.assert_state_with_diff(
20859        r#"
20860        use some::mod1;
20861        use some::mod2;
20862
20863      - const A: u32 = 42;
20864        ˇconst B: u32 = 42;
20865        const C: u32 = 42;
20866
20867
20868        fn main() {
20869            println!("hello");
20870
20871            println!("world");
20872        }
20873      "#
20874        .unindent(),
20875    );
20876
20877    cx.update_editor(|editor, window, cx| {
20878        editor.delete_line(&DeleteLine, window, cx);
20879    });
20880    executor.run_until_parked();
20881    cx.assert_state_with_diff(
20882        r#"
20883        use some::mod1;
20884        use some::mod2;
20885
20886      - const A: u32 = 42;
20887      - const B: u32 = 42;
20888        ˇconst C: u32 = 42;
20889
20890
20891        fn main() {
20892            println!("hello");
20893
20894            println!("world");
20895        }
20896      "#
20897        .unindent(),
20898    );
20899
20900    cx.update_editor(|editor, window, cx| {
20901        editor.delete_line(&DeleteLine, window, cx);
20902    });
20903    executor.run_until_parked();
20904    cx.assert_state_with_diff(
20905        r#"
20906        use some::mod1;
20907        use some::mod2;
20908
20909      - const A: u32 = 42;
20910      - const B: u32 = 42;
20911      - const C: u32 = 42;
20912        ˇ
20913
20914        fn main() {
20915            println!("hello");
20916
20917            println!("world");
20918        }
20919      "#
20920        .unindent(),
20921    );
20922
20923    cx.update_editor(|editor, window, cx| {
20924        editor.handle_input("replacement", window, cx);
20925    });
20926    executor.run_until_parked();
20927    cx.assert_state_with_diff(
20928        r#"
20929        use some::mod1;
20930        use some::mod2;
20931
20932      - const A: u32 = 42;
20933      - const B: u32 = 42;
20934      - const C: u32 = 42;
20935      -
20936      + replacementˇ
20937
20938        fn main() {
20939            println!("hello");
20940
20941            println!("world");
20942        }
20943      "#
20944        .unindent(),
20945    );
20946}
20947
20948#[gpui::test]
20949async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20950    init_test(cx, |_| {});
20951
20952    let mut cx = EditorTestContext::new(cx).await;
20953
20954    let base_text = r#"
20955        one
20956        two
20957        three
20958        four
20959        five
20960    "#
20961    .unindent();
20962    executor.run_until_parked();
20963    cx.set_state(
20964        &r#"
20965        one
20966        two
20967        fˇour
20968        five
20969        "#
20970        .unindent(),
20971    );
20972
20973    cx.set_head_text(&base_text);
20974    executor.run_until_parked();
20975
20976    cx.update_editor(|editor, window, cx| {
20977        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20978    });
20979    executor.run_until_parked();
20980
20981    cx.assert_state_with_diff(
20982        r#"
20983          one
20984          two
20985        - three
20986          fˇour
20987          five
20988        "#
20989        .unindent(),
20990    );
20991
20992    cx.update_editor(|editor, window, cx| {
20993        editor.backspace(&Backspace, window, cx);
20994        editor.backspace(&Backspace, window, cx);
20995    });
20996    executor.run_until_parked();
20997    cx.assert_state_with_diff(
20998        r#"
20999          one
21000          two
21001        - threeˇ
21002        - four
21003        + our
21004          five
21005        "#
21006        .unindent(),
21007    );
21008}
21009
21010#[gpui::test]
21011async fn test_edit_after_expanded_modification_hunk(
21012    executor: BackgroundExecutor,
21013    cx: &mut TestAppContext,
21014) {
21015    init_test(cx, |_| {});
21016
21017    let mut cx = EditorTestContext::new(cx).await;
21018
21019    let diff_base = r#"
21020        use some::mod1;
21021        use some::mod2;
21022
21023        const A: u32 = 42;
21024        const B: u32 = 42;
21025        const C: u32 = 42;
21026        const D: u32 = 42;
21027
21028
21029        fn main() {
21030            println!("hello");
21031
21032            println!("world");
21033        }"#
21034    .unindent();
21035
21036    cx.set_state(
21037        &r#"
21038        use some::mod1;
21039        use some::mod2;
21040
21041        const A: u32 = 42;
21042        const B: u32 = 42;
21043        const C: u32 = 43ˇ
21044        const D: u32 = 42;
21045
21046
21047        fn main() {
21048            println!("hello");
21049
21050            println!("world");
21051        }"#
21052        .unindent(),
21053    );
21054
21055    cx.set_head_text(&diff_base);
21056    executor.run_until_parked();
21057    cx.update_editor(|editor, window, cx| {
21058        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21059    });
21060    executor.run_until_parked();
21061
21062    cx.assert_state_with_diff(
21063        r#"
21064        use some::mod1;
21065        use some::mod2;
21066
21067        const A: u32 = 42;
21068        const B: u32 = 42;
21069      - const C: u32 = 42;
21070      + const C: u32 = 43ˇ
21071        const D: u32 = 42;
21072
21073
21074        fn main() {
21075            println!("hello");
21076
21077            println!("world");
21078        }"#
21079        .unindent(),
21080    );
21081
21082    cx.update_editor(|editor, window, cx| {
21083        editor.handle_input("\nnew_line\n", window, cx);
21084    });
21085    executor.run_until_parked();
21086
21087    cx.assert_state_with_diff(
21088        r#"
21089        use some::mod1;
21090        use some::mod2;
21091
21092        const A: u32 = 42;
21093        const B: u32 = 42;
21094      - const C: u32 = 42;
21095      + const C: u32 = 43
21096      + new_line
21097      + ˇ
21098        const D: u32 = 42;
21099
21100
21101        fn main() {
21102            println!("hello");
21103
21104            println!("world");
21105        }"#
21106        .unindent(),
21107    );
21108}
21109
21110#[gpui::test]
21111async fn test_stage_and_unstage_added_file_hunk(
21112    executor: BackgroundExecutor,
21113    cx: &mut TestAppContext,
21114) {
21115    init_test(cx, |_| {});
21116
21117    let mut cx = EditorTestContext::new(cx).await;
21118    cx.update_editor(|editor, _, cx| {
21119        editor.set_expand_all_diff_hunks(cx);
21120    });
21121
21122    let working_copy = r#"
21123            ˇfn main() {
21124                println!("hello, world!");
21125            }
21126        "#
21127    .unindent();
21128
21129    cx.set_state(&working_copy);
21130    executor.run_until_parked();
21131
21132    cx.assert_state_with_diff(
21133        r#"
21134            + ˇfn main() {
21135            +     println!("hello, world!");
21136            + }
21137        "#
21138        .unindent(),
21139    );
21140    cx.assert_index_text(None);
21141
21142    cx.update_editor(|editor, window, cx| {
21143        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21144    });
21145    executor.run_until_parked();
21146    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21147    cx.assert_state_with_diff(
21148        r#"
21149            + ˇfn main() {
21150            +     println!("hello, world!");
21151            + }
21152        "#
21153        .unindent(),
21154    );
21155
21156    cx.update_editor(|editor, window, cx| {
21157        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21158    });
21159    executor.run_until_parked();
21160    cx.assert_index_text(None);
21161}
21162
21163async fn setup_indent_guides_editor(
21164    text: &str,
21165    cx: &mut TestAppContext,
21166) -> (BufferId, EditorTestContext) {
21167    init_test(cx, |_| {});
21168
21169    let mut cx = EditorTestContext::new(cx).await;
21170
21171    let buffer_id = cx.update_editor(|editor, window, cx| {
21172        editor.set_text(text, window, cx);
21173        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21174
21175        buffer_ids[0]
21176    });
21177
21178    (buffer_id, cx)
21179}
21180
21181fn assert_indent_guides(
21182    range: Range<u32>,
21183    expected: Vec<IndentGuide>,
21184    active_indices: Option<Vec<usize>>,
21185    cx: &mut EditorTestContext,
21186) {
21187    let indent_guides = cx.update_editor(|editor, window, cx| {
21188        let snapshot = editor.snapshot(window, cx).display_snapshot;
21189        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21190            editor,
21191            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21192            true,
21193            &snapshot,
21194            cx,
21195        );
21196
21197        indent_guides.sort_by(|a, b| {
21198            a.depth.cmp(&b.depth).then(
21199                a.start_row
21200                    .cmp(&b.start_row)
21201                    .then(a.end_row.cmp(&b.end_row)),
21202            )
21203        });
21204        indent_guides
21205    });
21206
21207    if let Some(expected) = active_indices {
21208        let active_indices = cx.update_editor(|editor, window, cx| {
21209            let snapshot = editor.snapshot(window, cx).display_snapshot;
21210            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21211        });
21212
21213        assert_eq!(
21214            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21215            expected,
21216            "Active indent guide indices do not match"
21217        );
21218    }
21219
21220    assert_eq!(indent_guides, expected, "Indent guides do not match");
21221}
21222
21223fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21224    IndentGuide {
21225        buffer_id,
21226        start_row: MultiBufferRow(start_row),
21227        end_row: MultiBufferRow(end_row),
21228        depth,
21229        tab_size: 4,
21230        settings: IndentGuideSettings {
21231            enabled: true,
21232            line_width: 1,
21233            active_line_width: 1,
21234            coloring: IndentGuideColoring::default(),
21235            background_coloring: IndentGuideBackgroundColoring::default(),
21236        },
21237    }
21238}
21239
21240#[gpui::test]
21241async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21242    let (buffer_id, mut cx) = setup_indent_guides_editor(
21243        &"
21244        fn main() {
21245            let a = 1;
21246        }"
21247        .unindent(),
21248        cx,
21249    )
21250    .await;
21251
21252    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21253}
21254
21255#[gpui::test]
21256async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21257    let (buffer_id, mut cx) = setup_indent_guides_editor(
21258        &"
21259        fn main() {
21260            let a = 1;
21261            let b = 2;
21262        }"
21263        .unindent(),
21264        cx,
21265    )
21266    .await;
21267
21268    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21269}
21270
21271#[gpui::test]
21272async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21273    let (buffer_id, mut cx) = setup_indent_guides_editor(
21274        &"
21275        fn main() {
21276            let a = 1;
21277            if a == 3 {
21278                let b = 2;
21279            } else {
21280                let c = 3;
21281            }
21282        }"
21283        .unindent(),
21284        cx,
21285    )
21286    .await;
21287
21288    assert_indent_guides(
21289        0..8,
21290        vec![
21291            indent_guide(buffer_id, 1, 6, 0),
21292            indent_guide(buffer_id, 3, 3, 1),
21293            indent_guide(buffer_id, 5, 5, 1),
21294        ],
21295        None,
21296        &mut cx,
21297    );
21298}
21299
21300#[gpui::test]
21301async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21302    let (buffer_id, mut cx) = setup_indent_guides_editor(
21303        &"
21304        fn main() {
21305            let a = 1;
21306                let b = 2;
21307            let c = 3;
21308        }"
21309        .unindent(),
21310        cx,
21311    )
21312    .await;
21313
21314    assert_indent_guides(
21315        0..5,
21316        vec![
21317            indent_guide(buffer_id, 1, 3, 0),
21318            indent_guide(buffer_id, 2, 2, 1),
21319        ],
21320        None,
21321        &mut cx,
21322    );
21323}
21324
21325#[gpui::test]
21326async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21327    let (buffer_id, mut cx) = setup_indent_guides_editor(
21328        &"
21329        fn main() {
21330            let a = 1;
21331
21332            let c = 3;
21333        }"
21334        .unindent(),
21335        cx,
21336    )
21337    .await;
21338
21339    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21340}
21341
21342#[gpui::test]
21343async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21344    let (buffer_id, mut cx) = setup_indent_guides_editor(
21345        &"
21346        fn main() {
21347            let a = 1;
21348
21349            let c = 3;
21350
21351            if a == 3 {
21352                let b = 2;
21353            } else {
21354                let c = 3;
21355            }
21356        }"
21357        .unindent(),
21358        cx,
21359    )
21360    .await;
21361
21362    assert_indent_guides(
21363        0..11,
21364        vec![
21365            indent_guide(buffer_id, 1, 9, 0),
21366            indent_guide(buffer_id, 6, 6, 1),
21367            indent_guide(buffer_id, 8, 8, 1),
21368        ],
21369        None,
21370        &mut cx,
21371    );
21372}
21373
21374#[gpui::test]
21375async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21376    let (buffer_id, mut cx) = setup_indent_guides_editor(
21377        &"
21378        fn main() {
21379            let a = 1;
21380
21381            let c = 3;
21382
21383            if a == 3 {
21384                let b = 2;
21385            } else {
21386                let c = 3;
21387            }
21388        }"
21389        .unindent(),
21390        cx,
21391    )
21392    .await;
21393
21394    assert_indent_guides(
21395        1..11,
21396        vec![
21397            indent_guide(buffer_id, 1, 9, 0),
21398            indent_guide(buffer_id, 6, 6, 1),
21399            indent_guide(buffer_id, 8, 8, 1),
21400        ],
21401        None,
21402        &mut cx,
21403    );
21404}
21405
21406#[gpui::test]
21407async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21408    let (buffer_id, mut cx) = setup_indent_guides_editor(
21409        &"
21410        fn main() {
21411            let a = 1;
21412
21413            let c = 3;
21414
21415            if a == 3 {
21416                let b = 2;
21417            } else {
21418                let c = 3;
21419            }
21420        }"
21421        .unindent(),
21422        cx,
21423    )
21424    .await;
21425
21426    assert_indent_guides(
21427        1..10,
21428        vec![
21429            indent_guide(buffer_id, 1, 9, 0),
21430            indent_guide(buffer_id, 6, 6, 1),
21431            indent_guide(buffer_id, 8, 8, 1),
21432        ],
21433        None,
21434        &mut cx,
21435    );
21436}
21437
21438#[gpui::test]
21439async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21440    let (buffer_id, mut cx) = setup_indent_guides_editor(
21441        &"
21442        fn main() {
21443            if a {
21444                b(
21445                    c,
21446                    d,
21447                )
21448            } else {
21449                e(
21450                    f
21451                )
21452            }
21453        }"
21454        .unindent(),
21455        cx,
21456    )
21457    .await;
21458
21459    assert_indent_guides(
21460        0..11,
21461        vec![
21462            indent_guide(buffer_id, 1, 10, 0),
21463            indent_guide(buffer_id, 2, 5, 1),
21464            indent_guide(buffer_id, 7, 9, 1),
21465            indent_guide(buffer_id, 3, 4, 2),
21466            indent_guide(buffer_id, 8, 8, 2),
21467        ],
21468        None,
21469        &mut cx,
21470    );
21471
21472    cx.update_editor(|editor, window, cx| {
21473        editor.fold_at(MultiBufferRow(2), window, cx);
21474        assert_eq!(
21475            editor.display_text(cx),
21476            "
21477            fn main() {
21478                if a {
21479                    b(⋯
21480                    )
21481                } else {
21482                    e(
21483                        f
21484                    )
21485                }
21486            }"
21487            .unindent()
21488        );
21489    });
21490
21491    assert_indent_guides(
21492        0..11,
21493        vec![
21494            indent_guide(buffer_id, 1, 10, 0),
21495            indent_guide(buffer_id, 2, 5, 1),
21496            indent_guide(buffer_id, 7, 9, 1),
21497            indent_guide(buffer_id, 8, 8, 2),
21498        ],
21499        None,
21500        &mut cx,
21501    );
21502}
21503
21504#[gpui::test]
21505async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21506    let (buffer_id, mut cx) = setup_indent_guides_editor(
21507        &"
21508        block1
21509            block2
21510                block3
21511                    block4
21512            block2
21513        block1
21514        block1"
21515            .unindent(),
21516        cx,
21517    )
21518    .await;
21519
21520    assert_indent_guides(
21521        1..10,
21522        vec![
21523            indent_guide(buffer_id, 1, 4, 0),
21524            indent_guide(buffer_id, 2, 3, 1),
21525            indent_guide(buffer_id, 3, 3, 2),
21526        ],
21527        None,
21528        &mut cx,
21529    );
21530}
21531
21532#[gpui::test]
21533async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21534    let (buffer_id, mut cx) = setup_indent_guides_editor(
21535        &"
21536        block1
21537            block2
21538                block3
21539
21540        block1
21541        block1"
21542            .unindent(),
21543        cx,
21544    )
21545    .await;
21546
21547    assert_indent_guides(
21548        0..6,
21549        vec![
21550            indent_guide(buffer_id, 1, 2, 0),
21551            indent_guide(buffer_id, 2, 2, 1),
21552        ],
21553        None,
21554        &mut cx,
21555    );
21556}
21557
21558#[gpui::test]
21559async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21560    let (buffer_id, mut cx) = setup_indent_guides_editor(
21561        &"
21562        function component() {
21563        \treturn (
21564        \t\t\t
21565        \t\t<div>
21566        \t\t\t<abc></abc>
21567        \t\t</div>
21568        \t)
21569        }"
21570        .unindent(),
21571        cx,
21572    )
21573    .await;
21574
21575    assert_indent_guides(
21576        0..8,
21577        vec![
21578            indent_guide(buffer_id, 1, 6, 0),
21579            indent_guide(buffer_id, 2, 5, 1),
21580            indent_guide(buffer_id, 4, 4, 2),
21581        ],
21582        None,
21583        &mut cx,
21584    );
21585}
21586
21587#[gpui::test]
21588async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21589    let (buffer_id, mut cx) = setup_indent_guides_editor(
21590        &"
21591        function component() {
21592        \treturn (
21593        \t
21594        \t\t<div>
21595        \t\t\t<abc></abc>
21596        \t\t</div>
21597        \t)
21598        }"
21599        .unindent(),
21600        cx,
21601    )
21602    .await;
21603
21604    assert_indent_guides(
21605        0..8,
21606        vec![
21607            indent_guide(buffer_id, 1, 6, 0),
21608            indent_guide(buffer_id, 2, 5, 1),
21609            indent_guide(buffer_id, 4, 4, 2),
21610        ],
21611        None,
21612        &mut cx,
21613    );
21614}
21615
21616#[gpui::test]
21617async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21618    let (buffer_id, mut cx) = setup_indent_guides_editor(
21619        &"
21620        block1
21621
21622
21623
21624            block2
21625        "
21626        .unindent(),
21627        cx,
21628    )
21629    .await;
21630
21631    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21632}
21633
21634#[gpui::test]
21635async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21636    let (buffer_id, mut cx) = setup_indent_guides_editor(
21637        &"
21638        def a:
21639        \tb = 3
21640        \tif True:
21641        \t\tc = 4
21642        \t\td = 5
21643        \tprint(b)
21644        "
21645        .unindent(),
21646        cx,
21647    )
21648    .await;
21649
21650    assert_indent_guides(
21651        0..6,
21652        vec![
21653            indent_guide(buffer_id, 1, 5, 0),
21654            indent_guide(buffer_id, 3, 4, 1),
21655        ],
21656        None,
21657        &mut cx,
21658    );
21659}
21660
21661#[gpui::test]
21662async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21663    let (buffer_id, mut cx) = setup_indent_guides_editor(
21664        &"
21665    fn main() {
21666        let a = 1;
21667    }"
21668        .unindent(),
21669        cx,
21670    )
21671    .await;
21672
21673    cx.update_editor(|editor, window, cx| {
21674        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21675            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21676        });
21677    });
21678
21679    assert_indent_guides(
21680        0..3,
21681        vec![indent_guide(buffer_id, 1, 1, 0)],
21682        Some(vec![0]),
21683        &mut cx,
21684    );
21685}
21686
21687#[gpui::test]
21688async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21689    let (buffer_id, mut cx) = setup_indent_guides_editor(
21690        &"
21691    fn main() {
21692        if 1 == 2 {
21693            let a = 1;
21694        }
21695    }"
21696        .unindent(),
21697        cx,
21698    )
21699    .await;
21700
21701    cx.update_editor(|editor, window, cx| {
21702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21703            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21704        });
21705    });
21706
21707    assert_indent_guides(
21708        0..4,
21709        vec![
21710            indent_guide(buffer_id, 1, 3, 0),
21711            indent_guide(buffer_id, 2, 2, 1),
21712        ],
21713        Some(vec![1]),
21714        &mut cx,
21715    );
21716
21717    cx.update_editor(|editor, window, cx| {
21718        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21719            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21720        });
21721    });
21722
21723    assert_indent_guides(
21724        0..4,
21725        vec![
21726            indent_guide(buffer_id, 1, 3, 0),
21727            indent_guide(buffer_id, 2, 2, 1),
21728        ],
21729        Some(vec![1]),
21730        &mut cx,
21731    );
21732
21733    cx.update_editor(|editor, window, cx| {
21734        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21735            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21736        });
21737    });
21738
21739    assert_indent_guides(
21740        0..4,
21741        vec![
21742            indent_guide(buffer_id, 1, 3, 0),
21743            indent_guide(buffer_id, 2, 2, 1),
21744        ],
21745        Some(vec![0]),
21746        &mut cx,
21747    );
21748}
21749
21750#[gpui::test]
21751async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21752    let (buffer_id, mut cx) = setup_indent_guides_editor(
21753        &"
21754    fn main() {
21755        let a = 1;
21756
21757        let b = 2;
21758    }"
21759        .unindent(),
21760        cx,
21761    )
21762    .await;
21763
21764    cx.update_editor(|editor, window, cx| {
21765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21766            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21767        });
21768    });
21769
21770    assert_indent_guides(
21771        0..5,
21772        vec![indent_guide(buffer_id, 1, 3, 0)],
21773        Some(vec![0]),
21774        &mut cx,
21775    );
21776}
21777
21778#[gpui::test]
21779async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21780    let (buffer_id, mut cx) = setup_indent_guides_editor(
21781        &"
21782    def m:
21783        a = 1
21784        pass"
21785            .unindent(),
21786        cx,
21787    )
21788    .await;
21789
21790    cx.update_editor(|editor, window, cx| {
21791        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21792            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21793        });
21794    });
21795
21796    assert_indent_guides(
21797        0..3,
21798        vec![indent_guide(buffer_id, 1, 2, 0)],
21799        Some(vec![0]),
21800        &mut cx,
21801    );
21802}
21803
21804#[gpui::test]
21805async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21806    init_test(cx, |_| {});
21807    let mut cx = EditorTestContext::new(cx).await;
21808    let text = indoc! {
21809        "
21810        impl A {
21811            fn b() {
21812                0;
21813                3;
21814                5;
21815                6;
21816                7;
21817            }
21818        }
21819        "
21820    };
21821    let base_text = indoc! {
21822        "
21823        impl A {
21824            fn b() {
21825                0;
21826                1;
21827                2;
21828                3;
21829                4;
21830            }
21831            fn c() {
21832                5;
21833                6;
21834                7;
21835            }
21836        }
21837        "
21838    };
21839
21840    cx.update_editor(|editor, window, cx| {
21841        editor.set_text(text, window, cx);
21842
21843        editor.buffer().update(cx, |multibuffer, cx| {
21844            let buffer = multibuffer.as_singleton().unwrap();
21845            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21846
21847            multibuffer.set_all_diff_hunks_expanded(cx);
21848            multibuffer.add_diff(diff, cx);
21849
21850            buffer.read(cx).remote_id()
21851        })
21852    });
21853    cx.run_until_parked();
21854
21855    cx.assert_state_with_diff(
21856        indoc! { "
21857          impl A {
21858              fn b() {
21859                  0;
21860        -         1;
21861        -         2;
21862                  3;
21863        -         4;
21864        -     }
21865        -     fn c() {
21866                  5;
21867                  6;
21868                  7;
21869              }
21870          }
21871          ˇ"
21872        }
21873        .to_string(),
21874    );
21875
21876    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21877        editor
21878            .snapshot(window, cx)
21879            .buffer_snapshot()
21880            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21881            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21882            .collect::<Vec<_>>()
21883    });
21884    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21885    assert_eq!(
21886        actual_guides,
21887        vec![
21888            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21889            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21890            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21891        ]
21892    );
21893}
21894
21895#[gpui::test]
21896async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21897    init_test(cx, |_| {});
21898    let mut cx = EditorTestContext::new(cx).await;
21899
21900    let diff_base = r#"
21901        a
21902        b
21903        c
21904        "#
21905    .unindent();
21906
21907    cx.set_state(
21908        &r#"
21909        ˇA
21910        b
21911        C
21912        "#
21913        .unindent(),
21914    );
21915    cx.set_head_text(&diff_base);
21916    cx.update_editor(|editor, window, cx| {
21917        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21918    });
21919    executor.run_until_parked();
21920
21921    let both_hunks_expanded = r#"
21922        - a
21923        + ˇA
21924          b
21925        - c
21926        + C
21927        "#
21928    .unindent();
21929
21930    cx.assert_state_with_diff(both_hunks_expanded.clone());
21931
21932    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21933        let snapshot = editor.snapshot(window, cx);
21934        let hunks = editor
21935            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21936            .collect::<Vec<_>>();
21937        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21938        hunks
21939            .into_iter()
21940            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21941            .collect::<Vec<_>>()
21942    });
21943    assert_eq!(hunk_ranges.len(), 2);
21944
21945    cx.update_editor(|editor, _, cx| {
21946        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21947    });
21948    executor.run_until_parked();
21949
21950    let second_hunk_expanded = r#"
21951          ˇA
21952          b
21953        - c
21954        + C
21955        "#
21956    .unindent();
21957
21958    cx.assert_state_with_diff(second_hunk_expanded);
21959
21960    cx.update_editor(|editor, _, cx| {
21961        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21962    });
21963    executor.run_until_parked();
21964
21965    cx.assert_state_with_diff(both_hunks_expanded.clone());
21966
21967    cx.update_editor(|editor, _, cx| {
21968        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21969    });
21970    executor.run_until_parked();
21971
21972    let first_hunk_expanded = r#"
21973        - a
21974        + ˇA
21975          b
21976          C
21977        "#
21978    .unindent();
21979
21980    cx.assert_state_with_diff(first_hunk_expanded);
21981
21982    cx.update_editor(|editor, _, cx| {
21983        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21984    });
21985    executor.run_until_parked();
21986
21987    cx.assert_state_with_diff(both_hunks_expanded);
21988
21989    cx.set_state(
21990        &r#"
21991        ˇA
21992        b
21993        "#
21994        .unindent(),
21995    );
21996    cx.run_until_parked();
21997
21998    // TODO this cursor position seems bad
21999    cx.assert_state_with_diff(
22000        r#"
22001        - ˇa
22002        + A
22003          b
22004        "#
22005        .unindent(),
22006    );
22007
22008    cx.update_editor(|editor, window, cx| {
22009        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22010    });
22011
22012    cx.assert_state_with_diff(
22013        r#"
22014            - ˇa
22015            + A
22016              b
22017            - c
22018            "#
22019        .unindent(),
22020    );
22021
22022    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22023        let snapshot = editor.snapshot(window, cx);
22024        let hunks = editor
22025            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22026            .collect::<Vec<_>>();
22027        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22028        hunks
22029            .into_iter()
22030            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22031            .collect::<Vec<_>>()
22032    });
22033    assert_eq!(hunk_ranges.len(), 2);
22034
22035    cx.update_editor(|editor, _, cx| {
22036        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
22037    });
22038    executor.run_until_parked();
22039
22040    cx.assert_state_with_diff(
22041        r#"
22042        - ˇa
22043        + A
22044          b
22045        "#
22046        .unindent(),
22047    );
22048}
22049
22050#[gpui::test]
22051async fn test_toggle_deletion_hunk_at_start_of_file(
22052    executor: BackgroundExecutor,
22053    cx: &mut TestAppContext,
22054) {
22055    init_test(cx, |_| {});
22056    let mut cx = EditorTestContext::new(cx).await;
22057
22058    let diff_base = r#"
22059        a
22060        b
22061        c
22062        "#
22063    .unindent();
22064
22065    cx.set_state(
22066        &r#"
22067        ˇb
22068        c
22069        "#
22070        .unindent(),
22071    );
22072    cx.set_head_text(&diff_base);
22073    cx.update_editor(|editor, window, cx| {
22074        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
22075    });
22076    executor.run_until_parked();
22077
22078    let hunk_expanded = r#"
22079        - a
22080          ˇb
22081          c
22082        "#
22083    .unindent();
22084
22085    cx.assert_state_with_diff(hunk_expanded.clone());
22086
22087    let hunk_ranges = cx.update_editor(|editor, window, cx| {
22088        let snapshot = editor.snapshot(window, cx);
22089        let hunks = editor
22090            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22091            .collect::<Vec<_>>();
22092        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
22093        hunks
22094            .into_iter()
22095            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
22096            .collect::<Vec<_>>()
22097    });
22098    assert_eq!(hunk_ranges.len(), 1);
22099
22100    cx.update_editor(|editor, _, cx| {
22101        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22102    });
22103    executor.run_until_parked();
22104
22105    let hunk_collapsed = r#"
22106          ˇb
22107          c
22108        "#
22109    .unindent();
22110
22111    cx.assert_state_with_diff(hunk_collapsed);
22112
22113    cx.update_editor(|editor, _, cx| {
22114        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22115    });
22116    executor.run_until_parked();
22117
22118    cx.assert_state_with_diff(hunk_expanded);
22119}
22120
22121#[gpui::test]
22122async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22123    init_test(cx, |_| {});
22124
22125    let fs = FakeFs::new(cx.executor());
22126    fs.insert_tree(
22127        path!("/test"),
22128        json!({
22129            ".git": {},
22130            "file-1": "ONE\n",
22131            "file-2": "TWO\n",
22132            "file-3": "THREE\n",
22133        }),
22134    )
22135    .await;
22136
22137    fs.set_head_for_repo(
22138        path!("/test/.git").as_ref(),
22139        &[
22140            ("file-1", "one\n".into()),
22141            ("file-2", "two\n".into()),
22142            ("file-3", "three\n".into()),
22143        ],
22144        "deadbeef",
22145    );
22146
22147    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22148    let mut buffers = vec![];
22149    for i in 1..=3 {
22150        let buffer = project
22151            .update(cx, |project, cx| {
22152                let path = format!(path!("/test/file-{}"), i);
22153                project.open_local_buffer(path, cx)
22154            })
22155            .await
22156            .unwrap();
22157        buffers.push(buffer);
22158    }
22159
22160    let multibuffer = cx.new(|cx| {
22161        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22162        multibuffer.set_all_diff_hunks_expanded(cx);
22163        for buffer in &buffers {
22164            let snapshot = buffer.read(cx).snapshot();
22165            multibuffer.set_excerpts_for_path(
22166                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22167                buffer.clone(),
22168                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22169                2,
22170                cx,
22171            );
22172        }
22173        multibuffer
22174    });
22175
22176    let editor = cx.add_window(|window, cx| {
22177        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22178    });
22179    cx.run_until_parked();
22180
22181    let snapshot = editor
22182        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22183        .unwrap();
22184    let hunks = snapshot
22185        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22186        .map(|hunk| match hunk {
22187            DisplayDiffHunk::Unfolded {
22188                display_row_range, ..
22189            } => display_row_range,
22190            DisplayDiffHunk::Folded { .. } => unreachable!(),
22191        })
22192        .collect::<Vec<_>>();
22193    assert_eq!(
22194        hunks,
22195        [
22196            DisplayRow(2)..DisplayRow(4),
22197            DisplayRow(7)..DisplayRow(9),
22198            DisplayRow(12)..DisplayRow(14),
22199        ]
22200    );
22201}
22202
22203#[gpui::test]
22204async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22205    init_test(cx, |_| {});
22206
22207    let mut cx = EditorTestContext::new(cx).await;
22208    cx.set_head_text(indoc! { "
22209        one
22210        two
22211        three
22212        four
22213        five
22214        "
22215    });
22216    cx.set_index_text(indoc! { "
22217        one
22218        two
22219        three
22220        four
22221        five
22222        "
22223    });
22224    cx.set_state(indoc! {"
22225        one
22226        TWO
22227        ˇTHREE
22228        FOUR
22229        five
22230    "});
22231    cx.run_until_parked();
22232    cx.update_editor(|editor, window, cx| {
22233        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22234    });
22235    cx.run_until_parked();
22236    cx.assert_index_text(Some(indoc! {"
22237        one
22238        TWO
22239        THREE
22240        FOUR
22241        five
22242    "}));
22243    cx.set_state(indoc! { "
22244        one
22245        TWO
22246        ˇTHREE-HUNDRED
22247        FOUR
22248        five
22249    "});
22250    cx.run_until_parked();
22251    cx.update_editor(|editor, window, cx| {
22252        let snapshot = editor.snapshot(window, cx);
22253        let hunks = editor
22254            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22255            .collect::<Vec<_>>();
22256        assert_eq!(hunks.len(), 1);
22257        assert_eq!(
22258            hunks[0].status(),
22259            DiffHunkStatus {
22260                kind: DiffHunkStatusKind::Modified,
22261                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22262            }
22263        );
22264
22265        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22266    });
22267    cx.run_until_parked();
22268    cx.assert_index_text(Some(indoc! {"
22269        one
22270        TWO
22271        THREE-HUNDRED
22272        FOUR
22273        five
22274    "}));
22275}
22276
22277#[gpui::test]
22278fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22279    init_test(cx, |_| {});
22280
22281    let editor = cx.add_window(|window, cx| {
22282        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22283        build_editor(buffer, window, cx)
22284    });
22285
22286    let render_args = Arc::new(Mutex::new(None));
22287    let snapshot = editor
22288        .update(cx, |editor, window, cx| {
22289            let snapshot = editor.buffer().read(cx).snapshot(cx);
22290            let range =
22291                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22292
22293            struct RenderArgs {
22294                row: MultiBufferRow,
22295                folded: bool,
22296                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22297            }
22298
22299            let crease = Crease::inline(
22300                range,
22301                FoldPlaceholder::test(),
22302                {
22303                    let toggle_callback = render_args.clone();
22304                    move |row, folded, callback, _window, _cx| {
22305                        *toggle_callback.lock() = Some(RenderArgs {
22306                            row,
22307                            folded,
22308                            callback,
22309                        });
22310                        div()
22311                    }
22312                },
22313                |_row, _folded, _window, _cx| div(),
22314            );
22315
22316            editor.insert_creases(Some(crease), cx);
22317            let snapshot = editor.snapshot(window, cx);
22318            let _div =
22319                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22320            snapshot
22321        })
22322        .unwrap();
22323
22324    let render_args = render_args.lock().take().unwrap();
22325    assert_eq!(render_args.row, MultiBufferRow(1));
22326    assert!(!render_args.folded);
22327    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22328
22329    cx.update_window(*editor, |_, window, cx| {
22330        (render_args.callback)(true, window, cx)
22331    })
22332    .unwrap();
22333    let snapshot = editor
22334        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22335        .unwrap();
22336    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22337
22338    cx.update_window(*editor, |_, window, cx| {
22339        (render_args.callback)(false, window, cx)
22340    })
22341    .unwrap();
22342    let snapshot = editor
22343        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22344        .unwrap();
22345    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22346}
22347
22348#[gpui::test]
22349async fn test_input_text(cx: &mut TestAppContext) {
22350    init_test(cx, |_| {});
22351    let mut cx = EditorTestContext::new(cx).await;
22352
22353    cx.set_state(
22354        &r#"ˇone
22355        two
22356
22357        three
22358        fourˇ
22359        five
22360
22361        siˇx"#
22362            .unindent(),
22363    );
22364
22365    cx.dispatch_action(HandleInput(String::new()));
22366    cx.assert_editor_state(
22367        &r#"ˇone
22368        two
22369
22370        three
22371        fourˇ
22372        five
22373
22374        siˇx"#
22375            .unindent(),
22376    );
22377
22378    cx.dispatch_action(HandleInput("AAAA".to_string()));
22379    cx.assert_editor_state(
22380        &r#"AAAAˇone
22381        two
22382
22383        three
22384        fourAAAAˇ
22385        five
22386
22387        siAAAAˇx"#
22388            .unindent(),
22389    );
22390}
22391
22392#[gpui::test]
22393async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22394    init_test(cx, |_| {});
22395
22396    let mut cx = EditorTestContext::new(cx).await;
22397    cx.set_state(
22398        r#"let foo = 1;
22399let foo = 2;
22400let foo = 3;
22401let fooˇ = 4;
22402let foo = 5;
22403let foo = 6;
22404let foo = 7;
22405let foo = 8;
22406let foo = 9;
22407let foo = 10;
22408let foo = 11;
22409let foo = 12;
22410let foo = 13;
22411let foo = 14;
22412let foo = 15;"#,
22413    );
22414
22415    cx.update_editor(|e, window, cx| {
22416        assert_eq!(
22417            e.next_scroll_position,
22418            NextScrollCursorCenterTopBottom::Center,
22419            "Default next scroll direction is center",
22420        );
22421
22422        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22423        assert_eq!(
22424            e.next_scroll_position,
22425            NextScrollCursorCenterTopBottom::Top,
22426            "After center, next scroll direction should be top",
22427        );
22428
22429        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22430        assert_eq!(
22431            e.next_scroll_position,
22432            NextScrollCursorCenterTopBottom::Bottom,
22433            "After top, next scroll direction should be bottom",
22434        );
22435
22436        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22437        assert_eq!(
22438            e.next_scroll_position,
22439            NextScrollCursorCenterTopBottom::Center,
22440            "After bottom, scrolling should start over",
22441        );
22442
22443        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22444        assert_eq!(
22445            e.next_scroll_position,
22446            NextScrollCursorCenterTopBottom::Top,
22447            "Scrolling continues if retriggered fast enough"
22448        );
22449    });
22450
22451    cx.executor()
22452        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22453    cx.executor().run_until_parked();
22454    cx.update_editor(|e, _, _| {
22455        assert_eq!(
22456            e.next_scroll_position,
22457            NextScrollCursorCenterTopBottom::Center,
22458            "If scrolling is not triggered fast enough, it should reset"
22459        );
22460    });
22461}
22462
22463#[gpui::test]
22464async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22465    init_test(cx, |_| {});
22466    let mut cx = EditorLspTestContext::new_rust(
22467        lsp::ServerCapabilities {
22468            definition_provider: Some(lsp::OneOf::Left(true)),
22469            references_provider: Some(lsp::OneOf::Left(true)),
22470            ..lsp::ServerCapabilities::default()
22471        },
22472        cx,
22473    )
22474    .await;
22475
22476    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22477        let go_to_definition = cx
22478            .lsp
22479            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22480                move |params, _| async move {
22481                    if empty_go_to_definition {
22482                        Ok(None)
22483                    } else {
22484                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22485                            uri: params.text_document_position_params.text_document.uri,
22486                            range: lsp::Range::new(
22487                                lsp::Position::new(4, 3),
22488                                lsp::Position::new(4, 6),
22489                            ),
22490                        })))
22491                    }
22492                },
22493            );
22494        let references = cx
22495            .lsp
22496            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22497                Ok(Some(vec![lsp::Location {
22498                    uri: params.text_document_position.text_document.uri,
22499                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22500                }]))
22501            });
22502        (go_to_definition, references)
22503    };
22504
22505    cx.set_state(
22506        &r#"fn one() {
22507            let mut a = ˇtwo();
22508        }
22509
22510        fn two() {}"#
22511            .unindent(),
22512    );
22513    set_up_lsp_handlers(false, &mut cx);
22514    let navigated = cx
22515        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22516        .await
22517        .expect("Failed to navigate to definition");
22518    assert_eq!(
22519        navigated,
22520        Navigated::Yes,
22521        "Should have navigated to definition from the GetDefinition response"
22522    );
22523    cx.assert_editor_state(
22524        &r#"fn one() {
22525            let mut a = two();
22526        }
22527
22528        fn «twoˇ»() {}"#
22529            .unindent(),
22530    );
22531
22532    let editors = cx.update_workspace(|workspace, _, cx| {
22533        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22534    });
22535    cx.update_editor(|_, _, test_editor_cx| {
22536        assert_eq!(
22537            editors.len(),
22538            1,
22539            "Initially, only one, test, editor should be open in the workspace"
22540        );
22541        assert_eq!(
22542            test_editor_cx.entity(),
22543            editors.last().expect("Asserted len is 1").clone()
22544        );
22545    });
22546
22547    set_up_lsp_handlers(true, &mut cx);
22548    let navigated = cx
22549        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22550        .await
22551        .expect("Failed to navigate to lookup references");
22552    assert_eq!(
22553        navigated,
22554        Navigated::Yes,
22555        "Should have navigated to references as a fallback after empty GoToDefinition response"
22556    );
22557    // We should not change the selections in the existing file,
22558    // if opening another milti buffer with the references
22559    cx.assert_editor_state(
22560        &r#"fn one() {
22561            let mut a = two();
22562        }
22563
22564        fn «twoˇ»() {}"#
22565            .unindent(),
22566    );
22567    let editors = cx.update_workspace(|workspace, _, cx| {
22568        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22569    });
22570    cx.update_editor(|_, _, test_editor_cx| {
22571        assert_eq!(
22572            editors.len(),
22573            2,
22574            "After falling back to references search, we open a new editor with the results"
22575        );
22576        let references_fallback_text = editors
22577            .into_iter()
22578            .find(|new_editor| *new_editor != test_editor_cx.entity())
22579            .expect("Should have one non-test editor now")
22580            .read(test_editor_cx)
22581            .text(test_editor_cx);
22582        assert_eq!(
22583            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22584            "Should use the range from the references response and not the GoToDefinition one"
22585        );
22586    });
22587}
22588
22589#[gpui::test]
22590async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22591    init_test(cx, |_| {});
22592    cx.update(|cx| {
22593        let mut editor_settings = EditorSettings::get_global(cx).clone();
22594        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22595        EditorSettings::override_global(editor_settings, cx);
22596    });
22597    let mut cx = EditorLspTestContext::new_rust(
22598        lsp::ServerCapabilities {
22599            definition_provider: Some(lsp::OneOf::Left(true)),
22600            references_provider: Some(lsp::OneOf::Left(true)),
22601            ..lsp::ServerCapabilities::default()
22602        },
22603        cx,
22604    )
22605    .await;
22606    let original_state = r#"fn one() {
22607        let mut a = ˇtwo();
22608    }
22609
22610    fn two() {}"#
22611        .unindent();
22612    cx.set_state(&original_state);
22613
22614    let mut go_to_definition = cx
22615        .lsp
22616        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22617            move |_, _| async move { Ok(None) },
22618        );
22619    let _references = cx
22620        .lsp
22621        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22622            panic!("Should not call for references with no go to definition fallback")
22623        });
22624
22625    let navigated = cx
22626        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22627        .await
22628        .expect("Failed to navigate to lookup references");
22629    go_to_definition
22630        .next()
22631        .await
22632        .expect("Should have called the go_to_definition handler");
22633
22634    assert_eq!(
22635        navigated,
22636        Navigated::No,
22637        "Should have navigated to references as a fallback after empty GoToDefinition response"
22638    );
22639    cx.assert_editor_state(&original_state);
22640    let editors = cx.update_workspace(|workspace, _, cx| {
22641        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22642    });
22643    cx.update_editor(|_, _, _| {
22644        assert_eq!(
22645            editors.len(),
22646            1,
22647            "After unsuccessful fallback, no other editor should have been opened"
22648        );
22649    });
22650}
22651
22652#[gpui::test]
22653async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22654    init_test(cx, |_| {});
22655    let mut cx = EditorLspTestContext::new_rust(
22656        lsp::ServerCapabilities {
22657            references_provider: Some(lsp::OneOf::Left(true)),
22658            ..lsp::ServerCapabilities::default()
22659        },
22660        cx,
22661    )
22662    .await;
22663
22664    cx.set_state(
22665        &r#"
22666        fn one() {
22667            let mut a = two();
22668        }
22669
22670        fn ˇtwo() {}"#
22671            .unindent(),
22672    );
22673    cx.lsp
22674        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22675            Ok(Some(vec![
22676                lsp::Location {
22677                    uri: params.text_document_position.text_document.uri.clone(),
22678                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22679                },
22680                lsp::Location {
22681                    uri: params.text_document_position.text_document.uri,
22682                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22683                },
22684            ]))
22685        });
22686    let navigated = cx
22687        .update_editor(|editor, window, cx| {
22688            editor.find_all_references(&FindAllReferences::default(), window, cx)
22689        })
22690        .unwrap()
22691        .await
22692        .expect("Failed to navigate to references");
22693    assert_eq!(
22694        navigated,
22695        Navigated::Yes,
22696        "Should have navigated to references from the FindAllReferences response"
22697    );
22698    cx.assert_editor_state(
22699        &r#"fn one() {
22700            let mut a = two();
22701        }
22702
22703        fn ˇtwo() {}"#
22704            .unindent(),
22705    );
22706
22707    let editors = cx.update_workspace(|workspace, _, cx| {
22708        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22709    });
22710    cx.update_editor(|_, _, _| {
22711        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22712    });
22713
22714    cx.set_state(
22715        &r#"fn one() {
22716            let mut a = ˇtwo();
22717        }
22718
22719        fn two() {}"#
22720            .unindent(),
22721    );
22722    let navigated = cx
22723        .update_editor(|editor, window, cx| {
22724            editor.find_all_references(&FindAllReferences::default(), window, cx)
22725        })
22726        .unwrap()
22727        .await
22728        .expect("Failed to navigate to references");
22729    assert_eq!(
22730        navigated,
22731        Navigated::Yes,
22732        "Should have navigated to references from the FindAllReferences response"
22733    );
22734    cx.assert_editor_state(
22735        &r#"fn one() {
22736            let mut a = ˇtwo();
22737        }
22738
22739        fn two() {}"#
22740            .unindent(),
22741    );
22742    let editors = cx.update_workspace(|workspace, _, cx| {
22743        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22744    });
22745    cx.update_editor(|_, _, _| {
22746        assert_eq!(
22747            editors.len(),
22748            2,
22749            "should have re-used the previous multibuffer"
22750        );
22751    });
22752
22753    cx.set_state(
22754        &r#"fn one() {
22755            let mut a = ˇtwo();
22756        }
22757        fn three() {}
22758        fn two() {}"#
22759            .unindent(),
22760    );
22761    cx.lsp
22762        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22763            Ok(Some(vec![
22764                lsp::Location {
22765                    uri: params.text_document_position.text_document.uri.clone(),
22766                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22767                },
22768                lsp::Location {
22769                    uri: params.text_document_position.text_document.uri,
22770                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22771                },
22772            ]))
22773        });
22774    let navigated = cx
22775        .update_editor(|editor, window, cx| {
22776            editor.find_all_references(&FindAllReferences::default(), window, cx)
22777        })
22778        .unwrap()
22779        .await
22780        .expect("Failed to navigate to references");
22781    assert_eq!(
22782        navigated,
22783        Navigated::Yes,
22784        "Should have navigated to references from the FindAllReferences response"
22785    );
22786    cx.assert_editor_state(
22787        &r#"fn one() {
22788                let mut a = ˇtwo();
22789            }
22790            fn three() {}
22791            fn two() {}"#
22792            .unindent(),
22793    );
22794    let editors = cx.update_workspace(|workspace, _, cx| {
22795        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22796    });
22797    cx.update_editor(|_, _, _| {
22798        assert_eq!(
22799            editors.len(),
22800            3,
22801            "should have used a new multibuffer as offsets changed"
22802        );
22803    });
22804}
22805#[gpui::test]
22806async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22807    init_test(cx, |_| {});
22808
22809    let language = Arc::new(Language::new(
22810        LanguageConfig::default(),
22811        Some(tree_sitter_rust::LANGUAGE.into()),
22812    ));
22813
22814    let text = r#"
22815        #[cfg(test)]
22816        mod tests() {
22817            #[test]
22818            fn runnable_1() {
22819                let a = 1;
22820            }
22821
22822            #[test]
22823            fn runnable_2() {
22824                let a = 1;
22825                let b = 2;
22826            }
22827        }
22828    "#
22829    .unindent();
22830
22831    let fs = FakeFs::new(cx.executor());
22832    fs.insert_file("/file.rs", Default::default()).await;
22833
22834    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22835    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22836    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22837    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22838    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22839
22840    let editor = cx.new_window_entity(|window, cx| {
22841        Editor::new(
22842            EditorMode::full(),
22843            multi_buffer,
22844            Some(project.clone()),
22845            window,
22846            cx,
22847        )
22848    });
22849
22850    editor.update_in(cx, |editor, window, cx| {
22851        let snapshot = editor.buffer().read(cx).snapshot(cx);
22852        editor.tasks.insert(
22853            (buffer.read(cx).remote_id(), 3),
22854            RunnableTasks {
22855                templates: vec![],
22856                offset: snapshot.anchor_before(MultiBufferOffset(43)),
22857                column: 0,
22858                extra_variables: HashMap::default(),
22859                context_range: BufferOffset(43)..BufferOffset(85),
22860            },
22861        );
22862        editor.tasks.insert(
22863            (buffer.read(cx).remote_id(), 8),
22864            RunnableTasks {
22865                templates: vec![],
22866                offset: snapshot.anchor_before(MultiBufferOffset(86)),
22867                column: 0,
22868                extra_variables: HashMap::default(),
22869                context_range: BufferOffset(86)..BufferOffset(191),
22870            },
22871        );
22872
22873        // Test finding task when cursor is inside function body
22874        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22875            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22876        });
22877        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22878        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22879
22880        // Test finding task when cursor is on function name
22881        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22882            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22883        });
22884        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22885        assert_eq!(row, 8, "Should find task when cursor is on function name");
22886    });
22887}
22888
22889#[gpui::test]
22890async fn test_folding_buffers(cx: &mut TestAppContext) {
22891    init_test(cx, |_| {});
22892
22893    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22894    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22895    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22896
22897    let fs = FakeFs::new(cx.executor());
22898    fs.insert_tree(
22899        path!("/a"),
22900        json!({
22901            "first.rs": sample_text_1,
22902            "second.rs": sample_text_2,
22903            "third.rs": sample_text_3,
22904        }),
22905    )
22906    .await;
22907    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22908    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22909    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22910    let worktree = project.update(cx, |project, cx| {
22911        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22912        assert_eq!(worktrees.len(), 1);
22913        worktrees.pop().unwrap()
22914    });
22915    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22916
22917    let buffer_1 = project
22918        .update(cx, |project, cx| {
22919            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22920        })
22921        .await
22922        .unwrap();
22923    let buffer_2 = project
22924        .update(cx, |project, cx| {
22925            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22926        })
22927        .await
22928        .unwrap();
22929    let buffer_3 = project
22930        .update(cx, |project, cx| {
22931            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22932        })
22933        .await
22934        .unwrap();
22935
22936    let multi_buffer = cx.new(|cx| {
22937        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22938        multi_buffer.push_excerpts(
22939            buffer_1.clone(),
22940            [
22941                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22942                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22943                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22944            ],
22945            cx,
22946        );
22947        multi_buffer.push_excerpts(
22948            buffer_2.clone(),
22949            [
22950                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22951                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22952                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22953            ],
22954            cx,
22955        );
22956        multi_buffer.push_excerpts(
22957            buffer_3.clone(),
22958            [
22959                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22960                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22961                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22962            ],
22963            cx,
22964        );
22965        multi_buffer
22966    });
22967    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22968        Editor::new(
22969            EditorMode::full(),
22970            multi_buffer.clone(),
22971            Some(project.clone()),
22972            window,
22973            cx,
22974        )
22975    });
22976
22977    assert_eq!(
22978        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22979        "\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",
22980    );
22981
22982    multi_buffer_editor.update(cx, |editor, cx| {
22983        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22984    });
22985    assert_eq!(
22986        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22987        "\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",
22988        "After folding the first buffer, its text should not be displayed"
22989    );
22990
22991    multi_buffer_editor.update(cx, |editor, cx| {
22992        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22993    });
22994    assert_eq!(
22995        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22996        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22997        "After folding the second buffer, its text should not be displayed"
22998    );
22999
23000    multi_buffer_editor.update(cx, |editor, cx| {
23001        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23002    });
23003    assert_eq!(
23004        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23005        "\n\n\n\n\n",
23006        "After folding the third buffer, its text should not be displayed"
23007    );
23008
23009    // Emulate selection inside the fold logic, that should work
23010    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23011        editor
23012            .snapshot(window, cx)
23013            .next_line_boundary(Point::new(0, 4));
23014    });
23015
23016    multi_buffer_editor.update(cx, |editor, cx| {
23017        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23018    });
23019    assert_eq!(
23020        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23021        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
23022        "After unfolding the second buffer, its text should be displayed"
23023    );
23024
23025    // Typing inside of buffer 1 causes that buffer to be unfolded.
23026    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23027        assert_eq!(
23028            multi_buffer
23029                .read(cx)
23030                .snapshot(cx)
23031                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
23032                .collect::<String>(),
23033            "bbbb"
23034        );
23035        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23036            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
23037        });
23038        editor.handle_input("B", window, cx);
23039    });
23040
23041    assert_eq!(
23042        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23043        "\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",
23044        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
23045    );
23046
23047    multi_buffer_editor.update(cx, |editor, cx| {
23048        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23049    });
23050    assert_eq!(
23051        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23052        "\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",
23053        "After unfolding the all buffers, all original text should be displayed"
23054    );
23055}
23056
23057#[gpui::test]
23058async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
23059    init_test(cx, |_| {});
23060
23061    let sample_text_1 = "1111\n2222\n3333".to_string();
23062    let sample_text_2 = "4444\n5555\n6666".to_string();
23063    let sample_text_3 = "7777\n8888\n9999".to_string();
23064
23065    let fs = FakeFs::new(cx.executor());
23066    fs.insert_tree(
23067        path!("/a"),
23068        json!({
23069            "first.rs": sample_text_1,
23070            "second.rs": sample_text_2,
23071            "third.rs": sample_text_3,
23072        }),
23073    )
23074    .await;
23075    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23076    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23077    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23078    let worktree = project.update(cx, |project, cx| {
23079        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23080        assert_eq!(worktrees.len(), 1);
23081        worktrees.pop().unwrap()
23082    });
23083    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23084
23085    let buffer_1 = project
23086        .update(cx, |project, cx| {
23087            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
23088        })
23089        .await
23090        .unwrap();
23091    let buffer_2 = project
23092        .update(cx, |project, cx| {
23093            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
23094        })
23095        .await
23096        .unwrap();
23097    let buffer_3 = project
23098        .update(cx, |project, cx| {
23099            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
23100        })
23101        .await
23102        .unwrap();
23103
23104    let multi_buffer = cx.new(|cx| {
23105        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23106        multi_buffer.push_excerpts(
23107            buffer_1.clone(),
23108            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23109            cx,
23110        );
23111        multi_buffer.push_excerpts(
23112            buffer_2.clone(),
23113            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23114            cx,
23115        );
23116        multi_buffer.push_excerpts(
23117            buffer_3.clone(),
23118            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23119            cx,
23120        );
23121        multi_buffer
23122    });
23123
23124    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23125        Editor::new(
23126            EditorMode::full(),
23127            multi_buffer,
23128            Some(project.clone()),
23129            window,
23130            cx,
23131        )
23132    });
23133
23134    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23135    assert_eq!(
23136        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23137        full_text,
23138    );
23139
23140    multi_buffer_editor.update(cx, |editor, cx| {
23141        editor.fold_buffer(buffer_1.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\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23146        "After folding the first buffer, its text should not be displayed"
23147    );
23148
23149    multi_buffer_editor.update(cx, |editor, cx| {
23150        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23151    });
23152
23153    assert_eq!(
23154        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23155        "\n\n\n\n\n\n7777\n8888\n9999",
23156        "After folding the second buffer, its text should not be displayed"
23157    );
23158
23159    multi_buffer_editor.update(cx, |editor, cx| {
23160        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23161    });
23162    assert_eq!(
23163        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23164        "\n\n\n\n\n",
23165        "After folding the third buffer, its text should not be displayed"
23166    );
23167
23168    multi_buffer_editor.update(cx, |editor, cx| {
23169        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23170    });
23171    assert_eq!(
23172        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23173        "\n\n\n\n4444\n5555\n6666\n\n",
23174        "After unfolding the second buffer, its text should be displayed"
23175    );
23176
23177    multi_buffer_editor.update(cx, |editor, cx| {
23178        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23179    });
23180    assert_eq!(
23181        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23182        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23183        "After unfolding the first buffer, its text should be displayed"
23184    );
23185
23186    multi_buffer_editor.update(cx, |editor, cx| {
23187        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23188    });
23189    assert_eq!(
23190        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23191        full_text,
23192        "After unfolding all buffers, all original text should be displayed"
23193    );
23194}
23195
23196#[gpui::test]
23197async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23198    init_test(cx, |_| {});
23199
23200    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23201
23202    let fs = FakeFs::new(cx.executor());
23203    fs.insert_tree(
23204        path!("/a"),
23205        json!({
23206            "main.rs": sample_text,
23207        }),
23208    )
23209    .await;
23210    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23211    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23212    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23213    let worktree = project.update(cx, |project, cx| {
23214        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23215        assert_eq!(worktrees.len(), 1);
23216        worktrees.pop().unwrap()
23217    });
23218    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23219
23220    let buffer_1 = project
23221        .update(cx, |project, cx| {
23222            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23223        })
23224        .await
23225        .unwrap();
23226
23227    let multi_buffer = cx.new(|cx| {
23228        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23229        multi_buffer.push_excerpts(
23230            buffer_1.clone(),
23231            [ExcerptRange::new(
23232                Point::new(0, 0)
23233                    ..Point::new(
23234                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23235                        0,
23236                    ),
23237            )],
23238            cx,
23239        );
23240        multi_buffer
23241    });
23242    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23243        Editor::new(
23244            EditorMode::full(),
23245            multi_buffer,
23246            Some(project.clone()),
23247            window,
23248            cx,
23249        )
23250    });
23251
23252    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23253    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23254        enum TestHighlight {}
23255        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23256        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23257        editor.highlight_text::<TestHighlight>(
23258            vec![highlight_range.clone()],
23259            HighlightStyle::color(Hsla::green()),
23260            cx,
23261        );
23262        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23263            s.select_ranges(Some(highlight_range))
23264        });
23265    });
23266
23267    let full_text = format!("\n\n{sample_text}");
23268    assert_eq!(
23269        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23270        full_text,
23271    );
23272}
23273
23274#[gpui::test]
23275async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23276    init_test(cx, |_| {});
23277    cx.update(|cx| {
23278        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23279            "keymaps/default-linux.json",
23280            cx,
23281        )
23282        .unwrap();
23283        cx.bind_keys(default_key_bindings);
23284    });
23285
23286    let (editor, cx) = cx.add_window_view(|window, cx| {
23287        let multi_buffer = MultiBuffer::build_multi(
23288            [
23289                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23290                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23291                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23292                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23293            ],
23294            cx,
23295        );
23296        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23297
23298        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23299        // fold all but the second buffer, so that we test navigating between two
23300        // adjacent folded buffers, as well as folded buffers at the start and
23301        // end the multibuffer
23302        editor.fold_buffer(buffer_ids[0], cx);
23303        editor.fold_buffer(buffer_ids[2], cx);
23304        editor.fold_buffer(buffer_ids[3], cx);
23305
23306        editor
23307    });
23308    cx.simulate_resize(size(px(1000.), px(1000.)));
23309
23310    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23311    cx.assert_excerpts_with_selections(indoc! {"
23312        [EXCERPT]
23313        ˇ[FOLDED]
23314        [EXCERPT]
23315        a1
23316        b1
23317        [EXCERPT]
23318        [FOLDED]
23319        [EXCERPT]
23320        [FOLDED]
23321        "
23322    });
23323    cx.simulate_keystroke("down");
23324    cx.assert_excerpts_with_selections(indoc! {"
23325        [EXCERPT]
23326        [FOLDED]
23327        [EXCERPT]
23328        ˇa1
23329        b1
23330        [EXCERPT]
23331        [FOLDED]
23332        [EXCERPT]
23333        [FOLDED]
23334        "
23335    });
23336    cx.simulate_keystroke("down");
23337    cx.assert_excerpts_with_selections(indoc! {"
23338        [EXCERPT]
23339        [FOLDED]
23340        [EXCERPT]
23341        a1
23342        ˇb1
23343        [EXCERPT]
23344        [FOLDED]
23345        [EXCERPT]
23346        [FOLDED]
23347        "
23348    });
23349    cx.simulate_keystroke("down");
23350    cx.assert_excerpts_with_selections(indoc! {"
23351        [EXCERPT]
23352        [FOLDED]
23353        [EXCERPT]
23354        a1
23355        b1
23356        ˇ[EXCERPT]
23357        [FOLDED]
23358        [EXCERPT]
23359        [FOLDED]
23360        "
23361    });
23362    cx.simulate_keystroke("down");
23363    cx.assert_excerpts_with_selections(indoc! {"
23364        [EXCERPT]
23365        [FOLDED]
23366        [EXCERPT]
23367        a1
23368        b1
23369        [EXCERPT]
23370        ˇ[FOLDED]
23371        [EXCERPT]
23372        [FOLDED]
23373        "
23374    });
23375    for _ in 0..5 {
23376        cx.simulate_keystroke("down");
23377        cx.assert_excerpts_with_selections(indoc! {"
23378            [EXCERPT]
23379            [FOLDED]
23380            [EXCERPT]
23381            a1
23382            b1
23383            [EXCERPT]
23384            [FOLDED]
23385            [EXCERPT]
23386            ˇ[FOLDED]
23387            "
23388        });
23389    }
23390
23391    cx.simulate_keystroke("up");
23392    cx.assert_excerpts_with_selections(indoc! {"
23393        [EXCERPT]
23394        [FOLDED]
23395        [EXCERPT]
23396        a1
23397        b1
23398        [EXCERPT]
23399        ˇ[FOLDED]
23400        [EXCERPT]
23401        [FOLDED]
23402        "
23403    });
23404    cx.simulate_keystroke("up");
23405    cx.assert_excerpts_with_selections(indoc! {"
23406        [EXCERPT]
23407        [FOLDED]
23408        [EXCERPT]
23409        a1
23410        b1
23411        ˇ[EXCERPT]
23412        [FOLDED]
23413        [EXCERPT]
23414        [FOLDED]
23415        "
23416    });
23417    cx.simulate_keystroke("up");
23418    cx.assert_excerpts_with_selections(indoc! {"
23419        [EXCERPT]
23420        [FOLDED]
23421        [EXCERPT]
23422        a1
23423        ˇb1
23424        [EXCERPT]
23425        [FOLDED]
23426        [EXCERPT]
23427        [FOLDED]
23428        "
23429    });
23430    cx.simulate_keystroke("up");
23431    cx.assert_excerpts_with_selections(indoc! {"
23432        [EXCERPT]
23433        [FOLDED]
23434        [EXCERPT]
23435        ˇa1
23436        b1
23437        [EXCERPT]
23438        [FOLDED]
23439        [EXCERPT]
23440        [FOLDED]
23441        "
23442    });
23443    for _ in 0..5 {
23444        cx.simulate_keystroke("up");
23445        cx.assert_excerpts_with_selections(indoc! {"
23446            [EXCERPT]
23447            ˇ[FOLDED]
23448            [EXCERPT]
23449            a1
23450            b1
23451            [EXCERPT]
23452            [FOLDED]
23453            [EXCERPT]
23454            [FOLDED]
23455            "
23456        });
23457    }
23458}
23459
23460#[gpui::test]
23461async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23462    init_test(cx, |_| {});
23463
23464    // Simple insertion
23465    assert_highlighted_edits(
23466        "Hello, world!",
23467        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23468        true,
23469        cx,
23470        |highlighted_edits, cx| {
23471            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23472            assert_eq!(highlighted_edits.highlights.len(), 1);
23473            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23474            assert_eq!(
23475                highlighted_edits.highlights[0].1.background_color,
23476                Some(cx.theme().status().created_background)
23477            );
23478        },
23479    )
23480    .await;
23481
23482    // Replacement
23483    assert_highlighted_edits(
23484        "This is a test.",
23485        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23486        false,
23487        cx,
23488        |highlighted_edits, cx| {
23489            assert_eq!(highlighted_edits.text, "That is a test.");
23490            assert_eq!(highlighted_edits.highlights.len(), 1);
23491            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23492            assert_eq!(
23493                highlighted_edits.highlights[0].1.background_color,
23494                Some(cx.theme().status().created_background)
23495            );
23496        },
23497    )
23498    .await;
23499
23500    // Multiple edits
23501    assert_highlighted_edits(
23502        "Hello, world!",
23503        vec![
23504            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23505            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23506        ],
23507        false,
23508        cx,
23509        |highlighted_edits, cx| {
23510            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23511            assert_eq!(highlighted_edits.highlights.len(), 2);
23512            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23513            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23514            assert_eq!(
23515                highlighted_edits.highlights[0].1.background_color,
23516                Some(cx.theme().status().created_background)
23517            );
23518            assert_eq!(
23519                highlighted_edits.highlights[1].1.background_color,
23520                Some(cx.theme().status().created_background)
23521            );
23522        },
23523    )
23524    .await;
23525
23526    // Multiple lines with edits
23527    assert_highlighted_edits(
23528        "First line\nSecond line\nThird line\nFourth line",
23529        vec![
23530            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23531            (
23532                Point::new(2, 0)..Point::new(2, 10),
23533                "New third line".to_string(),
23534            ),
23535            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23536        ],
23537        false,
23538        cx,
23539        |highlighted_edits, cx| {
23540            assert_eq!(
23541                highlighted_edits.text,
23542                "Second modified\nNew third line\nFourth updated line"
23543            );
23544            assert_eq!(highlighted_edits.highlights.len(), 3);
23545            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23546            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23547            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23548            for highlight in &highlighted_edits.highlights {
23549                assert_eq!(
23550                    highlight.1.background_color,
23551                    Some(cx.theme().status().created_background)
23552                );
23553            }
23554        },
23555    )
23556    .await;
23557}
23558
23559#[gpui::test]
23560async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23561    init_test(cx, |_| {});
23562
23563    // Deletion
23564    assert_highlighted_edits(
23565        "Hello, world!",
23566        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23567        true,
23568        cx,
23569        |highlighted_edits, cx| {
23570            assert_eq!(highlighted_edits.text, "Hello, world!");
23571            assert_eq!(highlighted_edits.highlights.len(), 1);
23572            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23573            assert_eq!(
23574                highlighted_edits.highlights[0].1.background_color,
23575                Some(cx.theme().status().deleted_background)
23576            );
23577        },
23578    )
23579    .await;
23580
23581    // Insertion
23582    assert_highlighted_edits(
23583        "Hello, world!",
23584        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23585        true,
23586        cx,
23587        |highlighted_edits, cx| {
23588            assert_eq!(highlighted_edits.highlights.len(), 1);
23589            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23590            assert_eq!(
23591                highlighted_edits.highlights[0].1.background_color,
23592                Some(cx.theme().status().created_background)
23593            );
23594        },
23595    )
23596    .await;
23597}
23598
23599async fn assert_highlighted_edits(
23600    text: &str,
23601    edits: Vec<(Range<Point>, String)>,
23602    include_deletions: bool,
23603    cx: &mut TestAppContext,
23604    assertion_fn: impl Fn(HighlightedText, &App),
23605) {
23606    let window = cx.add_window(|window, cx| {
23607        let buffer = MultiBuffer::build_simple(text, cx);
23608        Editor::new(EditorMode::full(), buffer, None, window, cx)
23609    });
23610    let cx = &mut VisualTestContext::from_window(*window, cx);
23611
23612    let (buffer, snapshot) = window
23613        .update(cx, |editor, _window, cx| {
23614            (
23615                editor.buffer().clone(),
23616                editor.buffer().read(cx).snapshot(cx),
23617            )
23618        })
23619        .unwrap();
23620
23621    let edits = edits
23622        .into_iter()
23623        .map(|(range, edit)| {
23624            (
23625                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23626                edit,
23627            )
23628        })
23629        .collect::<Vec<_>>();
23630
23631    let text_anchor_edits = edits
23632        .clone()
23633        .into_iter()
23634        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23635        .collect::<Vec<_>>();
23636
23637    let edit_preview = window
23638        .update(cx, |_, _window, cx| {
23639            buffer
23640                .read(cx)
23641                .as_singleton()
23642                .unwrap()
23643                .read(cx)
23644                .preview_edits(text_anchor_edits.into(), cx)
23645        })
23646        .unwrap()
23647        .await;
23648
23649    cx.update(|_window, cx| {
23650        let highlighted_edits = edit_prediction_edit_text(
23651            snapshot.as_singleton().unwrap().2,
23652            &edits,
23653            &edit_preview,
23654            include_deletions,
23655            cx,
23656        );
23657        assertion_fn(highlighted_edits, cx)
23658    });
23659}
23660
23661#[track_caller]
23662fn assert_breakpoint(
23663    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23664    path: &Arc<Path>,
23665    expected: Vec<(u32, Breakpoint)>,
23666) {
23667    if expected.is_empty() {
23668        assert!(!breakpoints.contains_key(path), "{}", path.display());
23669    } else {
23670        let mut breakpoint = breakpoints
23671            .get(path)
23672            .unwrap()
23673            .iter()
23674            .map(|breakpoint| {
23675                (
23676                    breakpoint.row,
23677                    Breakpoint {
23678                        message: breakpoint.message.clone(),
23679                        state: breakpoint.state,
23680                        condition: breakpoint.condition.clone(),
23681                        hit_condition: breakpoint.hit_condition.clone(),
23682                    },
23683                )
23684            })
23685            .collect::<Vec<_>>();
23686
23687        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23688
23689        assert_eq!(expected, breakpoint);
23690    }
23691}
23692
23693fn add_log_breakpoint_at_cursor(
23694    editor: &mut Editor,
23695    log_message: &str,
23696    window: &mut Window,
23697    cx: &mut Context<Editor>,
23698) {
23699    let (anchor, bp) = editor
23700        .breakpoints_at_cursors(window, cx)
23701        .first()
23702        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23703        .unwrap_or_else(|| {
23704            let snapshot = editor.snapshot(window, cx);
23705            let cursor_position: Point =
23706                editor.selections.newest(&snapshot.display_snapshot).head();
23707
23708            let breakpoint_position = snapshot
23709                .buffer_snapshot()
23710                .anchor_before(Point::new(cursor_position.row, 0));
23711
23712            (breakpoint_position, Breakpoint::new_log(log_message))
23713        });
23714
23715    editor.edit_breakpoint_at_anchor(
23716        anchor,
23717        bp,
23718        BreakpointEditAction::EditLogMessage(log_message.into()),
23719        cx,
23720    );
23721}
23722
23723#[gpui::test]
23724async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23725    init_test(cx, |_| {});
23726
23727    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23728    let fs = FakeFs::new(cx.executor());
23729    fs.insert_tree(
23730        path!("/a"),
23731        json!({
23732            "main.rs": sample_text,
23733        }),
23734    )
23735    .await;
23736    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23737    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23738    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23739
23740    let fs = FakeFs::new(cx.executor());
23741    fs.insert_tree(
23742        path!("/a"),
23743        json!({
23744            "main.rs": sample_text,
23745        }),
23746    )
23747    .await;
23748    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23749    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23750    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23751    let worktree_id = workspace
23752        .update(cx, |workspace, _window, cx| {
23753            workspace.project().update(cx, |project, cx| {
23754                project.worktrees(cx).next().unwrap().read(cx).id()
23755            })
23756        })
23757        .unwrap();
23758
23759    let buffer = project
23760        .update(cx, |project, cx| {
23761            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23762        })
23763        .await
23764        .unwrap();
23765
23766    let (editor, cx) = cx.add_window_view(|window, cx| {
23767        Editor::new(
23768            EditorMode::full(),
23769            MultiBuffer::build_from_buffer(buffer, cx),
23770            Some(project.clone()),
23771            window,
23772            cx,
23773        )
23774    });
23775
23776    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23777    let abs_path = project.read_with(cx, |project, cx| {
23778        project
23779            .absolute_path(&project_path, cx)
23780            .map(Arc::from)
23781            .unwrap()
23782    });
23783
23784    // assert we can add breakpoint on the first line
23785    editor.update_in(cx, |editor, window, cx| {
23786        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23787        editor.move_to_end(&MoveToEnd, window, cx);
23788        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23789    });
23790
23791    let breakpoints = editor.update(cx, |editor, cx| {
23792        editor
23793            .breakpoint_store()
23794            .as_ref()
23795            .unwrap()
23796            .read(cx)
23797            .all_source_breakpoints(cx)
23798    });
23799
23800    assert_eq!(1, breakpoints.len());
23801    assert_breakpoint(
23802        &breakpoints,
23803        &abs_path,
23804        vec![
23805            (0, Breakpoint::new_standard()),
23806            (3, Breakpoint::new_standard()),
23807        ],
23808    );
23809
23810    editor.update_in(cx, |editor, window, cx| {
23811        editor.move_to_beginning(&MoveToBeginning, window, cx);
23812        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23813    });
23814
23815    let breakpoints = editor.update(cx, |editor, cx| {
23816        editor
23817            .breakpoint_store()
23818            .as_ref()
23819            .unwrap()
23820            .read(cx)
23821            .all_source_breakpoints(cx)
23822    });
23823
23824    assert_eq!(1, breakpoints.len());
23825    assert_breakpoint(
23826        &breakpoints,
23827        &abs_path,
23828        vec![(3, Breakpoint::new_standard())],
23829    );
23830
23831    editor.update_in(cx, |editor, window, cx| {
23832        editor.move_to_end(&MoveToEnd, window, cx);
23833        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23834    });
23835
23836    let breakpoints = editor.update(cx, |editor, cx| {
23837        editor
23838            .breakpoint_store()
23839            .as_ref()
23840            .unwrap()
23841            .read(cx)
23842            .all_source_breakpoints(cx)
23843    });
23844
23845    assert_eq!(0, breakpoints.len());
23846    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23847}
23848
23849#[gpui::test]
23850async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23851    init_test(cx, |_| {});
23852
23853    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23854
23855    let fs = FakeFs::new(cx.executor());
23856    fs.insert_tree(
23857        path!("/a"),
23858        json!({
23859            "main.rs": sample_text,
23860        }),
23861    )
23862    .await;
23863    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23864    let (workspace, cx) =
23865        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23866
23867    let worktree_id = workspace.update(cx, |workspace, cx| {
23868        workspace.project().update(cx, |project, cx| {
23869            project.worktrees(cx).next().unwrap().read(cx).id()
23870        })
23871    });
23872
23873    let buffer = project
23874        .update(cx, |project, cx| {
23875            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23876        })
23877        .await
23878        .unwrap();
23879
23880    let (editor, cx) = cx.add_window_view(|window, cx| {
23881        Editor::new(
23882            EditorMode::full(),
23883            MultiBuffer::build_from_buffer(buffer, cx),
23884            Some(project.clone()),
23885            window,
23886            cx,
23887        )
23888    });
23889
23890    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23891    let abs_path = project.read_with(cx, |project, cx| {
23892        project
23893            .absolute_path(&project_path, cx)
23894            .map(Arc::from)
23895            .unwrap()
23896    });
23897
23898    editor.update_in(cx, |editor, window, cx| {
23899        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23900    });
23901
23902    let breakpoints = editor.update(cx, |editor, cx| {
23903        editor
23904            .breakpoint_store()
23905            .as_ref()
23906            .unwrap()
23907            .read(cx)
23908            .all_source_breakpoints(cx)
23909    });
23910
23911    assert_breakpoint(
23912        &breakpoints,
23913        &abs_path,
23914        vec![(0, Breakpoint::new_log("hello world"))],
23915    );
23916
23917    // Removing a log message from a log breakpoint should remove it
23918    editor.update_in(cx, |editor, window, cx| {
23919        add_log_breakpoint_at_cursor(editor, "", window, cx);
23920    });
23921
23922    let breakpoints = editor.update(cx, |editor, cx| {
23923        editor
23924            .breakpoint_store()
23925            .as_ref()
23926            .unwrap()
23927            .read(cx)
23928            .all_source_breakpoints(cx)
23929    });
23930
23931    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23932
23933    editor.update_in(cx, |editor, window, cx| {
23934        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23935        editor.move_to_end(&MoveToEnd, window, cx);
23936        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23937        // Not adding a log message to a standard breakpoint shouldn't remove it
23938        add_log_breakpoint_at_cursor(editor, "", window, cx);
23939    });
23940
23941    let breakpoints = editor.update(cx, |editor, cx| {
23942        editor
23943            .breakpoint_store()
23944            .as_ref()
23945            .unwrap()
23946            .read(cx)
23947            .all_source_breakpoints(cx)
23948    });
23949
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        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23961    });
23962
23963    let breakpoints = editor.update(cx, |editor, cx| {
23964        editor
23965            .breakpoint_store()
23966            .as_ref()
23967            .unwrap()
23968            .read(cx)
23969            .all_source_breakpoints(cx)
23970    });
23971
23972    assert_breakpoint(
23973        &breakpoints,
23974        &abs_path,
23975        vec![
23976            (0, Breakpoint::new_standard()),
23977            (3, Breakpoint::new_log("hello world")),
23978        ],
23979    );
23980
23981    editor.update_in(cx, |editor, window, cx| {
23982        add_log_breakpoint_at_cursor(editor, "hello Earth!!", 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_breakpoint(
23995        &breakpoints,
23996        &abs_path,
23997        vec![
23998            (0, Breakpoint::new_standard()),
23999            (3, Breakpoint::new_log("hello Earth!!")),
24000        ],
24001    );
24002}
24003
24004/// This also tests that Editor::breakpoint_at_cursor_head is working properly
24005/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
24006/// or when breakpoints were placed out of order. This tests for a regression too
24007#[gpui::test]
24008async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
24009    init_test(cx, |_| {});
24010
24011    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
24012    let fs = FakeFs::new(cx.executor());
24013    fs.insert_tree(
24014        path!("/a"),
24015        json!({
24016            "main.rs": sample_text,
24017        }),
24018    )
24019    .await;
24020    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24021    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24022    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24023
24024    let fs = FakeFs::new(cx.executor());
24025    fs.insert_tree(
24026        path!("/a"),
24027        json!({
24028            "main.rs": sample_text,
24029        }),
24030    )
24031    .await;
24032    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24033    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24034    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24035    let worktree_id = workspace
24036        .update(cx, |workspace, _window, cx| {
24037            workspace.project().update(cx, |project, cx| {
24038                project.worktrees(cx).next().unwrap().read(cx).id()
24039            })
24040        })
24041        .unwrap();
24042
24043    let buffer = project
24044        .update(cx, |project, cx| {
24045            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
24046        })
24047        .await
24048        .unwrap();
24049
24050    let (editor, cx) = cx.add_window_view(|window, cx| {
24051        Editor::new(
24052            EditorMode::full(),
24053            MultiBuffer::build_from_buffer(buffer, cx),
24054            Some(project.clone()),
24055            window,
24056            cx,
24057        )
24058    });
24059
24060    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
24061    let abs_path = project.read_with(cx, |project, cx| {
24062        project
24063            .absolute_path(&project_path, cx)
24064            .map(Arc::from)
24065            .unwrap()
24066    });
24067
24068    // assert we can add breakpoint on the first line
24069    editor.update_in(cx, |editor, window, cx| {
24070        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24071        editor.move_to_end(&MoveToEnd, window, cx);
24072        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24073        editor.move_up(&MoveUp, window, cx);
24074        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
24075    });
24076
24077    let breakpoints = editor.update(cx, |editor, cx| {
24078        editor
24079            .breakpoint_store()
24080            .as_ref()
24081            .unwrap()
24082            .read(cx)
24083            .all_source_breakpoints(cx)
24084    });
24085
24086    assert_eq!(1, breakpoints.len());
24087    assert_breakpoint(
24088        &breakpoints,
24089        &abs_path,
24090        vec![
24091            (0, Breakpoint::new_standard()),
24092            (2, Breakpoint::new_standard()),
24093            (3, Breakpoint::new_standard()),
24094        ],
24095    );
24096
24097    editor.update_in(cx, |editor, window, cx| {
24098        editor.move_to_beginning(&MoveToBeginning, window, cx);
24099        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24100        editor.move_to_end(&MoveToEnd, window, cx);
24101        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24102        // Disabling a breakpoint that doesn't exist should do nothing
24103        editor.move_up(&MoveUp, window, cx);
24104        editor.move_up(&MoveUp, window, cx);
24105        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24106    });
24107
24108    let breakpoints = editor.update(cx, |editor, cx| {
24109        editor
24110            .breakpoint_store()
24111            .as_ref()
24112            .unwrap()
24113            .read(cx)
24114            .all_source_breakpoints(cx)
24115    });
24116
24117    let disable_breakpoint = {
24118        let mut bp = Breakpoint::new_standard();
24119        bp.state = BreakpointState::Disabled;
24120        bp
24121    };
24122
24123    assert_eq!(1, breakpoints.len());
24124    assert_breakpoint(
24125        &breakpoints,
24126        &abs_path,
24127        vec![
24128            (0, disable_breakpoint.clone()),
24129            (2, Breakpoint::new_standard()),
24130            (3, disable_breakpoint.clone()),
24131        ],
24132    );
24133
24134    editor.update_in(cx, |editor, window, cx| {
24135        editor.move_to_beginning(&MoveToBeginning, window, cx);
24136        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24137        editor.move_to_end(&MoveToEnd, window, cx);
24138        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24139        editor.move_up(&MoveUp, window, cx);
24140        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24141    });
24142
24143    let breakpoints = editor.update(cx, |editor, cx| {
24144        editor
24145            .breakpoint_store()
24146            .as_ref()
24147            .unwrap()
24148            .read(cx)
24149            .all_source_breakpoints(cx)
24150    });
24151
24152    assert_eq!(1, breakpoints.len());
24153    assert_breakpoint(
24154        &breakpoints,
24155        &abs_path,
24156        vec![
24157            (0, Breakpoint::new_standard()),
24158            (2, disable_breakpoint),
24159            (3, Breakpoint::new_standard()),
24160        ],
24161    );
24162}
24163
24164#[gpui::test]
24165async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24166    init_test(cx, |_| {});
24167    let capabilities = lsp::ServerCapabilities {
24168        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24169            prepare_provider: Some(true),
24170            work_done_progress_options: Default::default(),
24171        })),
24172        ..Default::default()
24173    };
24174    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24175
24176    cx.set_state(indoc! {"
24177        struct Fˇoo {}
24178    "});
24179
24180    cx.update_editor(|editor, _, cx| {
24181        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24182        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24183        editor.highlight_background::<DocumentHighlightRead>(
24184            &[highlight_range],
24185            |_, theme| theme.colors().editor_document_highlight_read_background,
24186            cx,
24187        );
24188    });
24189
24190    let mut prepare_rename_handler = cx
24191        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24192            move |_, _, _| async move {
24193                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24194                    start: lsp::Position {
24195                        line: 0,
24196                        character: 7,
24197                    },
24198                    end: lsp::Position {
24199                        line: 0,
24200                        character: 10,
24201                    },
24202                })))
24203            },
24204        );
24205    let prepare_rename_task = cx
24206        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24207        .expect("Prepare rename was not started");
24208    prepare_rename_handler.next().await.unwrap();
24209    prepare_rename_task.await.expect("Prepare rename failed");
24210
24211    let mut rename_handler =
24212        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24213            let edit = lsp::TextEdit {
24214                range: lsp::Range {
24215                    start: lsp::Position {
24216                        line: 0,
24217                        character: 7,
24218                    },
24219                    end: lsp::Position {
24220                        line: 0,
24221                        character: 10,
24222                    },
24223                },
24224                new_text: "FooRenamed".to_string(),
24225            };
24226            Ok(Some(lsp::WorkspaceEdit::new(
24227                // Specify the same edit twice
24228                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24229            )))
24230        });
24231    let rename_task = cx
24232        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24233        .expect("Confirm rename was not started");
24234    rename_handler.next().await.unwrap();
24235    rename_task.await.expect("Confirm rename failed");
24236    cx.run_until_parked();
24237
24238    // Despite two edits, only one is actually applied as those are identical
24239    cx.assert_editor_state(indoc! {"
24240        struct FooRenamedˇ {}
24241    "});
24242}
24243
24244#[gpui::test]
24245async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24246    init_test(cx, |_| {});
24247    // These capabilities indicate that the server does not support prepare rename.
24248    let capabilities = lsp::ServerCapabilities {
24249        rename_provider: Some(lsp::OneOf::Left(true)),
24250        ..Default::default()
24251    };
24252    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24253
24254    cx.set_state(indoc! {"
24255        struct Fˇoo {}
24256    "});
24257
24258    cx.update_editor(|editor, _window, cx| {
24259        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24260        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24261        editor.highlight_background::<DocumentHighlightRead>(
24262            &[highlight_range],
24263            |_, theme| theme.colors().editor_document_highlight_read_background,
24264            cx,
24265        );
24266    });
24267
24268    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24269        .expect("Prepare rename was not started")
24270        .await
24271        .expect("Prepare rename failed");
24272
24273    let mut rename_handler =
24274        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24275            let edit = lsp::TextEdit {
24276                range: lsp::Range {
24277                    start: lsp::Position {
24278                        line: 0,
24279                        character: 7,
24280                    },
24281                    end: lsp::Position {
24282                        line: 0,
24283                        character: 10,
24284                    },
24285                },
24286                new_text: "FooRenamed".to_string(),
24287            };
24288            Ok(Some(lsp::WorkspaceEdit::new(
24289                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24290            )))
24291        });
24292    let rename_task = cx
24293        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24294        .expect("Confirm rename was not started");
24295    rename_handler.next().await.unwrap();
24296    rename_task.await.expect("Confirm rename failed");
24297    cx.run_until_parked();
24298
24299    // Correct range is renamed, as `surrounding_word` is used to find it.
24300    cx.assert_editor_state(indoc! {"
24301        struct FooRenamedˇ {}
24302    "});
24303}
24304
24305#[gpui::test]
24306async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24307    init_test(cx, |_| {});
24308    let mut cx = EditorTestContext::new(cx).await;
24309
24310    let language = Arc::new(
24311        Language::new(
24312            LanguageConfig::default(),
24313            Some(tree_sitter_html::LANGUAGE.into()),
24314        )
24315        .with_brackets_query(
24316            r#"
24317            ("<" @open "/>" @close)
24318            ("</" @open ">" @close)
24319            ("<" @open ">" @close)
24320            ("\"" @open "\"" @close)
24321            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24322        "#,
24323        )
24324        .unwrap(),
24325    );
24326    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24327
24328    cx.set_state(indoc! {"
24329        <span>ˇ</span>
24330    "});
24331    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24332    cx.assert_editor_state(indoc! {"
24333        <span>
24334        ˇ
24335        </span>
24336    "});
24337
24338    cx.set_state(indoc! {"
24339        <span><span></span>ˇ</span>
24340    "});
24341    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24342    cx.assert_editor_state(indoc! {"
24343        <span><span></span>
24344        ˇ</span>
24345    "});
24346
24347    cx.set_state(indoc! {"
24348        <span>ˇ
24349        </span>
24350    "});
24351    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24352    cx.assert_editor_state(indoc! {"
24353        <span>
24354        ˇ
24355        </span>
24356    "});
24357}
24358
24359#[gpui::test(iterations = 10)]
24360async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24361    init_test(cx, |_| {});
24362
24363    let fs = FakeFs::new(cx.executor());
24364    fs.insert_tree(
24365        path!("/dir"),
24366        json!({
24367            "a.ts": "a",
24368        }),
24369    )
24370    .await;
24371
24372    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24373    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24374    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24375
24376    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24377    language_registry.add(Arc::new(Language::new(
24378        LanguageConfig {
24379            name: "TypeScript".into(),
24380            matcher: LanguageMatcher {
24381                path_suffixes: vec!["ts".to_string()],
24382                ..Default::default()
24383            },
24384            ..Default::default()
24385        },
24386        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24387    )));
24388    let mut fake_language_servers = language_registry.register_fake_lsp(
24389        "TypeScript",
24390        FakeLspAdapter {
24391            capabilities: lsp::ServerCapabilities {
24392                code_lens_provider: Some(lsp::CodeLensOptions {
24393                    resolve_provider: Some(true),
24394                }),
24395                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24396                    commands: vec!["_the/command".to_string()],
24397                    ..lsp::ExecuteCommandOptions::default()
24398                }),
24399                ..lsp::ServerCapabilities::default()
24400            },
24401            ..FakeLspAdapter::default()
24402        },
24403    );
24404
24405    let editor = workspace
24406        .update(cx, |workspace, window, cx| {
24407            workspace.open_abs_path(
24408                PathBuf::from(path!("/dir/a.ts")),
24409                OpenOptions::default(),
24410                window,
24411                cx,
24412            )
24413        })
24414        .unwrap()
24415        .await
24416        .unwrap()
24417        .downcast::<Editor>()
24418        .unwrap();
24419    cx.executor().run_until_parked();
24420
24421    let fake_server = fake_language_servers.next().await.unwrap();
24422
24423    let buffer = editor.update(cx, |editor, cx| {
24424        editor
24425            .buffer()
24426            .read(cx)
24427            .as_singleton()
24428            .expect("have opened a single file by path")
24429    });
24430
24431    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24432    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24433    drop(buffer_snapshot);
24434    let actions = cx
24435        .update_window(*workspace, |_, window, cx| {
24436            project.code_actions(&buffer, anchor..anchor, window, cx)
24437        })
24438        .unwrap();
24439
24440    fake_server
24441        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24442            Ok(Some(vec![
24443                lsp::CodeLens {
24444                    range: lsp::Range::default(),
24445                    command: Some(lsp::Command {
24446                        title: "Code lens command".to_owned(),
24447                        command: "_the/command".to_owned(),
24448                        arguments: None,
24449                    }),
24450                    data: None,
24451                },
24452                lsp::CodeLens {
24453                    range: lsp::Range::default(),
24454                    command: Some(lsp::Command {
24455                        title: "Command not in capabilities".to_owned(),
24456                        command: "not in capabilities".to_owned(),
24457                        arguments: None,
24458                    }),
24459                    data: None,
24460                },
24461                lsp::CodeLens {
24462                    range: lsp::Range {
24463                        start: lsp::Position {
24464                            line: 1,
24465                            character: 1,
24466                        },
24467                        end: lsp::Position {
24468                            line: 1,
24469                            character: 1,
24470                        },
24471                    },
24472                    command: Some(lsp::Command {
24473                        title: "Command not in range".to_owned(),
24474                        command: "_the/command".to_owned(),
24475                        arguments: None,
24476                    }),
24477                    data: None,
24478                },
24479            ]))
24480        })
24481        .next()
24482        .await;
24483
24484    let actions = actions.await.unwrap();
24485    assert_eq!(
24486        actions.len(),
24487        1,
24488        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24489    );
24490    let action = actions[0].clone();
24491    let apply = project.update(cx, |project, cx| {
24492        project.apply_code_action(buffer.clone(), action, true, cx)
24493    });
24494
24495    // Resolving the code action does not populate its edits. In absence of
24496    // edits, we must execute the given command.
24497    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24498        |mut lens, _| async move {
24499            let lens_command = lens.command.as_mut().expect("should have a command");
24500            assert_eq!(lens_command.title, "Code lens command");
24501            lens_command.arguments = Some(vec![json!("the-argument")]);
24502            Ok(lens)
24503        },
24504    );
24505
24506    // While executing the command, the language server sends the editor
24507    // a `workspaceEdit` request.
24508    fake_server
24509        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24510            let fake = fake_server.clone();
24511            move |params, _| {
24512                assert_eq!(params.command, "_the/command");
24513                let fake = fake.clone();
24514                async move {
24515                    fake.server
24516                        .request::<lsp::request::ApplyWorkspaceEdit>(
24517                            lsp::ApplyWorkspaceEditParams {
24518                                label: None,
24519                                edit: lsp::WorkspaceEdit {
24520                                    changes: Some(
24521                                        [(
24522                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24523                                            vec![lsp::TextEdit {
24524                                                range: lsp::Range::new(
24525                                                    lsp::Position::new(0, 0),
24526                                                    lsp::Position::new(0, 0),
24527                                                ),
24528                                                new_text: "X".into(),
24529                                            }],
24530                                        )]
24531                                        .into_iter()
24532                                        .collect(),
24533                                    ),
24534                                    ..lsp::WorkspaceEdit::default()
24535                                },
24536                            },
24537                        )
24538                        .await
24539                        .into_response()
24540                        .unwrap();
24541                    Ok(Some(json!(null)))
24542                }
24543            }
24544        })
24545        .next()
24546        .await;
24547
24548    // Applying the code lens command returns a project transaction containing the edits
24549    // sent by the language server in its `workspaceEdit` request.
24550    let transaction = apply.await.unwrap();
24551    assert!(transaction.0.contains_key(&buffer));
24552    buffer.update(cx, |buffer, cx| {
24553        assert_eq!(buffer.text(), "Xa");
24554        buffer.undo(cx);
24555        assert_eq!(buffer.text(), "a");
24556    });
24557
24558    let actions_after_edits = cx
24559        .update_window(*workspace, |_, window, cx| {
24560            project.code_actions(&buffer, anchor..anchor, window, cx)
24561        })
24562        .unwrap()
24563        .await
24564        .unwrap();
24565    assert_eq!(
24566        actions, actions_after_edits,
24567        "For the same selection, same code lens actions should be returned"
24568    );
24569
24570    let _responses =
24571        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24572            panic!("No more code lens requests are expected");
24573        });
24574    editor.update_in(cx, |editor, window, cx| {
24575        editor.select_all(&SelectAll, window, cx);
24576    });
24577    cx.executor().run_until_parked();
24578    let new_actions = cx
24579        .update_window(*workspace, |_, window, cx| {
24580            project.code_actions(&buffer, anchor..anchor, window, cx)
24581        })
24582        .unwrap()
24583        .await
24584        .unwrap();
24585    assert_eq!(
24586        actions, new_actions,
24587        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24588    );
24589}
24590
24591#[gpui::test]
24592async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24593    init_test(cx, |_| {});
24594
24595    let fs = FakeFs::new(cx.executor());
24596    let main_text = r#"fn main() {
24597println!("1");
24598println!("2");
24599println!("3");
24600println!("4");
24601println!("5");
24602}"#;
24603    let lib_text = "mod foo {}";
24604    fs.insert_tree(
24605        path!("/a"),
24606        json!({
24607            "lib.rs": lib_text,
24608            "main.rs": main_text,
24609        }),
24610    )
24611    .await;
24612
24613    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24614    let (workspace, cx) =
24615        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24616    let worktree_id = workspace.update(cx, |workspace, cx| {
24617        workspace.project().update(cx, |project, cx| {
24618            project.worktrees(cx).next().unwrap().read(cx).id()
24619        })
24620    });
24621
24622    let expected_ranges = vec![
24623        Point::new(0, 0)..Point::new(0, 0),
24624        Point::new(1, 0)..Point::new(1, 1),
24625        Point::new(2, 0)..Point::new(2, 2),
24626        Point::new(3, 0)..Point::new(3, 3),
24627    ];
24628
24629    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24630    let editor_1 = workspace
24631        .update_in(cx, |workspace, window, cx| {
24632            workspace.open_path(
24633                (worktree_id, rel_path("main.rs")),
24634                Some(pane_1.downgrade()),
24635                true,
24636                window,
24637                cx,
24638            )
24639        })
24640        .unwrap()
24641        .await
24642        .downcast::<Editor>()
24643        .unwrap();
24644    pane_1.update(cx, |pane, cx| {
24645        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24646        open_editor.update(cx, |editor, cx| {
24647            assert_eq!(
24648                editor.display_text(cx),
24649                main_text,
24650                "Original main.rs text on initial open",
24651            );
24652            assert_eq!(
24653                editor
24654                    .selections
24655                    .all::<Point>(&editor.display_snapshot(cx))
24656                    .into_iter()
24657                    .map(|s| s.range())
24658                    .collect::<Vec<_>>(),
24659                vec![Point::zero()..Point::zero()],
24660                "Default selections on initial open",
24661            );
24662        })
24663    });
24664    editor_1.update_in(cx, |editor, window, cx| {
24665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24666            s.select_ranges(expected_ranges.clone());
24667        });
24668    });
24669
24670    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24671        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24672    });
24673    let editor_2 = workspace
24674        .update_in(cx, |workspace, window, cx| {
24675            workspace.open_path(
24676                (worktree_id, rel_path("main.rs")),
24677                Some(pane_2.downgrade()),
24678                true,
24679                window,
24680                cx,
24681            )
24682        })
24683        .unwrap()
24684        .await
24685        .downcast::<Editor>()
24686        .unwrap();
24687    pane_2.update(cx, |pane, cx| {
24688        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24689        open_editor.update(cx, |editor, cx| {
24690            assert_eq!(
24691                editor.display_text(cx),
24692                main_text,
24693                "Original main.rs text on initial open in another panel",
24694            );
24695            assert_eq!(
24696                editor
24697                    .selections
24698                    .all::<Point>(&editor.display_snapshot(cx))
24699                    .into_iter()
24700                    .map(|s| s.range())
24701                    .collect::<Vec<_>>(),
24702                vec![Point::zero()..Point::zero()],
24703                "Default selections on initial open in another panel",
24704            );
24705        })
24706    });
24707
24708    editor_2.update_in(cx, |editor, window, cx| {
24709        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24710    });
24711
24712    let _other_editor_1 = workspace
24713        .update_in(cx, |workspace, window, cx| {
24714            workspace.open_path(
24715                (worktree_id, rel_path("lib.rs")),
24716                Some(pane_1.downgrade()),
24717                true,
24718                window,
24719                cx,
24720            )
24721        })
24722        .unwrap()
24723        .await
24724        .downcast::<Editor>()
24725        .unwrap();
24726    pane_1
24727        .update_in(cx, |pane, window, cx| {
24728            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24729        })
24730        .await
24731        .unwrap();
24732    drop(editor_1);
24733    pane_1.update(cx, |pane, cx| {
24734        pane.active_item()
24735            .unwrap()
24736            .downcast::<Editor>()
24737            .unwrap()
24738            .update(cx, |editor, cx| {
24739                assert_eq!(
24740                    editor.display_text(cx),
24741                    lib_text,
24742                    "Other file should be open and active",
24743                );
24744            });
24745        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24746    });
24747
24748    let _other_editor_2 = workspace
24749        .update_in(cx, |workspace, window, cx| {
24750            workspace.open_path(
24751                (worktree_id, rel_path("lib.rs")),
24752                Some(pane_2.downgrade()),
24753                true,
24754                window,
24755                cx,
24756            )
24757        })
24758        .unwrap()
24759        .await
24760        .downcast::<Editor>()
24761        .unwrap();
24762    pane_2
24763        .update_in(cx, |pane, window, cx| {
24764            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24765        })
24766        .await
24767        .unwrap();
24768    drop(editor_2);
24769    pane_2.update(cx, |pane, cx| {
24770        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24771        open_editor.update(cx, |editor, cx| {
24772            assert_eq!(
24773                editor.display_text(cx),
24774                lib_text,
24775                "Other file should be open and active in another panel too",
24776            );
24777        });
24778        assert_eq!(
24779            pane.items().count(),
24780            1,
24781            "No other editors should be open in another pane",
24782        );
24783    });
24784
24785    let _editor_1_reopened = workspace
24786        .update_in(cx, |workspace, window, cx| {
24787            workspace.open_path(
24788                (worktree_id, rel_path("main.rs")),
24789                Some(pane_1.downgrade()),
24790                true,
24791                window,
24792                cx,
24793            )
24794        })
24795        .unwrap()
24796        .await
24797        .downcast::<Editor>()
24798        .unwrap();
24799    let _editor_2_reopened = workspace
24800        .update_in(cx, |workspace, window, cx| {
24801            workspace.open_path(
24802                (worktree_id, rel_path("main.rs")),
24803                Some(pane_2.downgrade()),
24804                true,
24805                window,
24806                cx,
24807            )
24808        })
24809        .unwrap()
24810        .await
24811        .downcast::<Editor>()
24812        .unwrap();
24813    pane_1.update(cx, |pane, cx| {
24814        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24815        open_editor.update(cx, |editor, cx| {
24816            assert_eq!(
24817                editor.display_text(cx),
24818                main_text,
24819                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24820            );
24821            assert_eq!(
24822                editor
24823                    .selections
24824                    .all::<Point>(&editor.display_snapshot(cx))
24825                    .into_iter()
24826                    .map(|s| s.range())
24827                    .collect::<Vec<_>>(),
24828                expected_ranges,
24829                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24830            );
24831        })
24832    });
24833    pane_2.update(cx, |pane, cx| {
24834        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24835        open_editor.update(cx, |editor, cx| {
24836            assert_eq!(
24837                editor.display_text(cx),
24838                r#"fn main() {
24839⋯rintln!("1");
24840⋯intln!("2");
24841⋯ntln!("3");
24842println!("4");
24843println!("5");
24844}"#,
24845                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24846            );
24847            assert_eq!(
24848                editor
24849                    .selections
24850                    .all::<Point>(&editor.display_snapshot(cx))
24851                    .into_iter()
24852                    .map(|s| s.range())
24853                    .collect::<Vec<_>>(),
24854                vec![Point::zero()..Point::zero()],
24855                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24856            );
24857        })
24858    });
24859}
24860
24861#[gpui::test]
24862async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24863    init_test(cx, |_| {});
24864
24865    let fs = FakeFs::new(cx.executor());
24866    let main_text = r#"fn main() {
24867println!("1");
24868println!("2");
24869println!("3");
24870println!("4");
24871println!("5");
24872}"#;
24873    let lib_text = "mod foo {}";
24874    fs.insert_tree(
24875        path!("/a"),
24876        json!({
24877            "lib.rs": lib_text,
24878            "main.rs": main_text,
24879        }),
24880    )
24881    .await;
24882
24883    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24884    let (workspace, cx) =
24885        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24886    let worktree_id = workspace.update(cx, |workspace, cx| {
24887        workspace.project().update(cx, |project, cx| {
24888            project.worktrees(cx).next().unwrap().read(cx).id()
24889        })
24890    });
24891
24892    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24893    let editor = workspace
24894        .update_in(cx, |workspace, window, cx| {
24895            workspace.open_path(
24896                (worktree_id, rel_path("main.rs")),
24897                Some(pane.downgrade()),
24898                true,
24899                window,
24900                cx,
24901            )
24902        })
24903        .unwrap()
24904        .await
24905        .downcast::<Editor>()
24906        .unwrap();
24907    pane.update(cx, |pane, cx| {
24908        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24909        open_editor.update(cx, |editor, cx| {
24910            assert_eq!(
24911                editor.display_text(cx),
24912                main_text,
24913                "Original main.rs text on initial open",
24914            );
24915        })
24916    });
24917    editor.update_in(cx, |editor, window, cx| {
24918        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24919    });
24920
24921    cx.update_global(|store: &mut SettingsStore, cx| {
24922        store.update_user_settings(cx, |s| {
24923            s.workspace.restore_on_file_reopen = Some(false);
24924        });
24925    });
24926    editor.update_in(cx, |editor, window, cx| {
24927        editor.fold_ranges(
24928            vec![
24929                Point::new(1, 0)..Point::new(1, 1),
24930                Point::new(2, 0)..Point::new(2, 2),
24931                Point::new(3, 0)..Point::new(3, 3),
24932            ],
24933            false,
24934            window,
24935            cx,
24936        );
24937    });
24938    pane.update_in(cx, |pane, window, cx| {
24939        pane.close_all_items(&CloseAllItems::default(), window, cx)
24940    })
24941    .await
24942    .unwrap();
24943    pane.update(cx, |pane, _| {
24944        assert!(pane.active_item().is_none());
24945    });
24946    cx.update_global(|store: &mut SettingsStore, cx| {
24947        store.update_user_settings(cx, |s| {
24948            s.workspace.restore_on_file_reopen = Some(true);
24949        });
24950    });
24951
24952    let _editor_reopened = workspace
24953        .update_in(cx, |workspace, window, cx| {
24954            workspace.open_path(
24955                (worktree_id, rel_path("main.rs")),
24956                Some(pane.downgrade()),
24957                true,
24958                window,
24959                cx,
24960            )
24961        })
24962        .unwrap()
24963        .await
24964        .downcast::<Editor>()
24965        .unwrap();
24966    pane.update(cx, |pane, cx| {
24967        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24968        open_editor.update(cx, |editor, cx| {
24969            assert_eq!(
24970                editor.display_text(cx),
24971                main_text,
24972                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24973            );
24974        })
24975    });
24976}
24977
24978#[gpui::test]
24979async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24980    struct EmptyModalView {
24981        focus_handle: gpui::FocusHandle,
24982    }
24983    impl EventEmitter<DismissEvent> for EmptyModalView {}
24984    impl Render for EmptyModalView {
24985        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24986            div()
24987        }
24988    }
24989    impl Focusable for EmptyModalView {
24990        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24991            self.focus_handle.clone()
24992        }
24993    }
24994    impl workspace::ModalView for EmptyModalView {}
24995    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24996        EmptyModalView {
24997            focus_handle: cx.focus_handle(),
24998        }
24999    }
25000
25001    init_test(cx, |_| {});
25002
25003    let fs = FakeFs::new(cx.executor());
25004    let project = Project::test(fs, [], cx).await;
25005    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25006    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
25007    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
25008    let editor = cx.new_window_entity(|window, cx| {
25009        Editor::new(
25010            EditorMode::full(),
25011            buffer,
25012            Some(project.clone()),
25013            window,
25014            cx,
25015        )
25016    });
25017    workspace
25018        .update(cx, |workspace, window, cx| {
25019            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
25020        })
25021        .unwrap();
25022    editor.update_in(cx, |editor, window, cx| {
25023        editor.open_context_menu(&OpenContextMenu, window, cx);
25024        assert!(editor.mouse_context_menu.is_some());
25025    });
25026    workspace
25027        .update(cx, |workspace, window, cx| {
25028            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
25029        })
25030        .unwrap();
25031    cx.read(|cx| {
25032        assert!(editor.read(cx).mouse_context_menu.is_none());
25033    });
25034}
25035
25036fn set_linked_edit_ranges(
25037    opening: (Point, Point),
25038    closing: (Point, Point),
25039    editor: &mut Editor,
25040    cx: &mut Context<Editor>,
25041) {
25042    let Some((buffer, _)) = editor
25043        .buffer
25044        .read(cx)
25045        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
25046    else {
25047        panic!("Failed to get buffer for selection position");
25048    };
25049    let buffer = buffer.read(cx);
25050    let buffer_id = buffer.remote_id();
25051    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
25052    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
25053    let mut linked_ranges = HashMap::default();
25054    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
25055    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
25056}
25057
25058#[gpui::test]
25059async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
25060    init_test(cx, |_| {});
25061
25062    let fs = FakeFs::new(cx.executor());
25063    fs.insert_file(path!("/file.html"), Default::default())
25064        .await;
25065
25066    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
25067
25068    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25069    let html_language = Arc::new(Language::new(
25070        LanguageConfig {
25071            name: "HTML".into(),
25072            matcher: LanguageMatcher {
25073                path_suffixes: vec!["html".to_string()],
25074                ..LanguageMatcher::default()
25075            },
25076            brackets: BracketPairConfig {
25077                pairs: vec![BracketPair {
25078                    start: "<".into(),
25079                    end: ">".into(),
25080                    close: true,
25081                    ..Default::default()
25082                }],
25083                ..Default::default()
25084            },
25085            ..Default::default()
25086        },
25087        Some(tree_sitter_html::LANGUAGE.into()),
25088    ));
25089    language_registry.add(html_language);
25090    let mut fake_servers = language_registry.register_fake_lsp(
25091        "HTML",
25092        FakeLspAdapter {
25093            capabilities: lsp::ServerCapabilities {
25094                completion_provider: Some(lsp::CompletionOptions {
25095                    resolve_provider: Some(true),
25096                    ..Default::default()
25097                }),
25098                ..Default::default()
25099            },
25100            ..Default::default()
25101        },
25102    );
25103
25104    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
25105    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25106
25107    let worktree_id = workspace
25108        .update(cx, |workspace, _window, cx| {
25109            workspace.project().update(cx, |project, cx| {
25110                project.worktrees(cx).next().unwrap().read(cx).id()
25111            })
25112        })
25113        .unwrap();
25114    project
25115        .update(cx, |project, cx| {
25116            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25117        })
25118        .await
25119        .unwrap();
25120    let editor = workspace
25121        .update(cx, |workspace, window, cx| {
25122            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25123        })
25124        .unwrap()
25125        .await
25126        .unwrap()
25127        .downcast::<Editor>()
25128        .unwrap();
25129
25130    let fake_server = fake_servers.next().await.unwrap();
25131    editor.update_in(cx, |editor, window, cx| {
25132        editor.set_text("<ad></ad>", window, cx);
25133        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25134            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25135        });
25136        set_linked_edit_ranges(
25137            (Point::new(0, 1), Point::new(0, 3)),
25138            (Point::new(0, 6), Point::new(0, 8)),
25139            editor,
25140            cx,
25141        );
25142    });
25143    let mut completion_handle =
25144        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25145            Ok(Some(lsp::CompletionResponse::Array(vec![
25146                lsp::CompletionItem {
25147                    label: "head".to_string(),
25148                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25149                        lsp::InsertReplaceEdit {
25150                            new_text: "head".to_string(),
25151                            insert: lsp::Range::new(
25152                                lsp::Position::new(0, 1),
25153                                lsp::Position::new(0, 3),
25154                            ),
25155                            replace: lsp::Range::new(
25156                                lsp::Position::new(0, 1),
25157                                lsp::Position::new(0, 3),
25158                            ),
25159                        },
25160                    )),
25161                    ..Default::default()
25162                },
25163            ])))
25164        });
25165    editor.update_in(cx, |editor, window, cx| {
25166        editor.show_completions(&ShowCompletions, window, cx);
25167    });
25168    cx.run_until_parked();
25169    completion_handle.next().await.unwrap();
25170    editor.update(cx, |editor, _| {
25171        assert!(
25172            editor.context_menu_visible(),
25173            "Completion menu should be visible"
25174        );
25175    });
25176    editor.update_in(cx, |editor, window, cx| {
25177        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25178    });
25179    cx.executor().run_until_parked();
25180    editor.update(cx, |editor, cx| {
25181        assert_eq!(editor.text(cx), "<head></head>");
25182    });
25183}
25184
25185#[gpui::test]
25186async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25187    init_test(cx, |_| {});
25188
25189    let mut cx = EditorTestContext::new(cx).await;
25190    let language = Arc::new(Language::new(
25191        LanguageConfig {
25192            name: "TSX".into(),
25193            matcher: LanguageMatcher {
25194                path_suffixes: vec!["tsx".to_string()],
25195                ..LanguageMatcher::default()
25196            },
25197            brackets: BracketPairConfig {
25198                pairs: vec![BracketPair {
25199                    start: "<".into(),
25200                    end: ">".into(),
25201                    close: true,
25202                    ..Default::default()
25203                }],
25204                ..Default::default()
25205            },
25206            linked_edit_characters: HashSet::from_iter(['.']),
25207            ..Default::default()
25208        },
25209        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25210    ));
25211    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25212
25213    // Test typing > does not extend linked pair
25214    cx.set_state("<divˇ<div></div>");
25215    cx.update_editor(|editor, _, cx| {
25216        set_linked_edit_ranges(
25217            (Point::new(0, 1), Point::new(0, 4)),
25218            (Point::new(0, 11), Point::new(0, 14)),
25219            editor,
25220            cx,
25221        );
25222    });
25223    cx.update_editor(|editor, window, cx| {
25224        editor.handle_input(">", window, cx);
25225    });
25226    cx.assert_editor_state("<div>ˇ<div></div>");
25227
25228    // Test typing . do extend linked pair
25229    cx.set_state("<Animatedˇ></Animated>");
25230    cx.update_editor(|editor, _, cx| {
25231        set_linked_edit_ranges(
25232            (Point::new(0, 1), Point::new(0, 9)),
25233            (Point::new(0, 12), Point::new(0, 20)),
25234            editor,
25235            cx,
25236        );
25237    });
25238    cx.update_editor(|editor, window, cx| {
25239        editor.handle_input(".", window, cx);
25240    });
25241    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25242    cx.update_editor(|editor, _, cx| {
25243        set_linked_edit_ranges(
25244            (Point::new(0, 1), Point::new(0, 10)),
25245            (Point::new(0, 13), Point::new(0, 21)),
25246            editor,
25247            cx,
25248        );
25249    });
25250    cx.update_editor(|editor, window, cx| {
25251        editor.handle_input("V", window, cx);
25252    });
25253    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25254}
25255
25256#[gpui::test]
25257async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25258    init_test(cx, |_| {});
25259
25260    let fs = FakeFs::new(cx.executor());
25261    fs.insert_tree(
25262        path!("/root"),
25263        json!({
25264            "a": {
25265                "main.rs": "fn main() {}",
25266            },
25267            "foo": {
25268                "bar": {
25269                    "external_file.rs": "pub mod external {}",
25270                }
25271            }
25272        }),
25273    )
25274    .await;
25275
25276    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25277    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25278    language_registry.add(rust_lang());
25279    let _fake_servers = language_registry.register_fake_lsp(
25280        "Rust",
25281        FakeLspAdapter {
25282            ..FakeLspAdapter::default()
25283        },
25284    );
25285    let (workspace, cx) =
25286        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25287    let worktree_id = workspace.update(cx, |workspace, cx| {
25288        workspace.project().update(cx, |project, cx| {
25289            project.worktrees(cx).next().unwrap().read(cx).id()
25290        })
25291    });
25292
25293    let assert_language_servers_count =
25294        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25295            project.update(cx, |project, cx| {
25296                let current = project
25297                    .lsp_store()
25298                    .read(cx)
25299                    .as_local()
25300                    .unwrap()
25301                    .language_servers
25302                    .len();
25303                assert_eq!(expected, current, "{context}");
25304            });
25305        };
25306
25307    assert_language_servers_count(
25308        0,
25309        "No servers should be running before any file is open",
25310        cx,
25311    );
25312    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25313    let main_editor = workspace
25314        .update_in(cx, |workspace, window, cx| {
25315            workspace.open_path(
25316                (worktree_id, rel_path("main.rs")),
25317                Some(pane.downgrade()),
25318                true,
25319                window,
25320                cx,
25321            )
25322        })
25323        .unwrap()
25324        .await
25325        .downcast::<Editor>()
25326        .unwrap();
25327    pane.update(cx, |pane, cx| {
25328        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25329        open_editor.update(cx, |editor, cx| {
25330            assert_eq!(
25331                editor.display_text(cx),
25332                "fn main() {}",
25333                "Original main.rs text on initial open",
25334            );
25335        });
25336        assert_eq!(open_editor, main_editor);
25337    });
25338    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25339
25340    let external_editor = workspace
25341        .update_in(cx, |workspace, window, cx| {
25342            workspace.open_abs_path(
25343                PathBuf::from("/root/foo/bar/external_file.rs"),
25344                OpenOptions::default(),
25345                window,
25346                cx,
25347            )
25348        })
25349        .await
25350        .expect("opening external file")
25351        .downcast::<Editor>()
25352        .expect("downcasted external file's open element to editor");
25353    pane.update(cx, |pane, cx| {
25354        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25355        open_editor.update(cx, |editor, cx| {
25356            assert_eq!(
25357                editor.display_text(cx),
25358                "pub mod external {}",
25359                "External file is open now",
25360            );
25361        });
25362        assert_eq!(open_editor, external_editor);
25363    });
25364    assert_language_servers_count(
25365        1,
25366        "Second, external, *.rs file should join the existing server",
25367        cx,
25368    );
25369
25370    pane.update_in(cx, |pane, window, cx| {
25371        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25372    })
25373    .await
25374    .unwrap();
25375    pane.update_in(cx, |pane, window, cx| {
25376        pane.navigate_backward(&Default::default(), window, cx);
25377    });
25378    cx.run_until_parked();
25379    pane.update(cx, |pane, cx| {
25380        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25381        open_editor.update(cx, |editor, cx| {
25382            assert_eq!(
25383                editor.display_text(cx),
25384                "pub mod external {}",
25385                "External file is open now",
25386            );
25387        });
25388    });
25389    assert_language_servers_count(
25390        1,
25391        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25392        cx,
25393    );
25394
25395    cx.update(|_, cx| {
25396        workspace::reload(cx);
25397    });
25398    assert_language_servers_count(
25399        1,
25400        "After reloading the worktree with local and external files opened, only one project should be started",
25401        cx,
25402    );
25403}
25404
25405#[gpui::test]
25406async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25407    init_test(cx, |_| {});
25408
25409    let mut cx = EditorTestContext::new(cx).await;
25410    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25411    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25412
25413    // test cursor move to start of each line on tab
25414    // for `if`, `elif`, `else`, `while`, `with` and `for`
25415    cx.set_state(indoc! {"
25416        def main():
25417        ˇ    for item in items:
25418        ˇ        while item.active:
25419        ˇ            if item.value > 10:
25420        ˇ                continue
25421        ˇ            elif item.value < 0:
25422        ˇ                break
25423        ˇ            else:
25424        ˇ                with item.context() as ctx:
25425        ˇ                    yield count
25426        ˇ        else:
25427        ˇ            log('while else')
25428        ˇ    else:
25429        ˇ        log('for else')
25430    "});
25431    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25432    cx.assert_editor_state(indoc! {"
25433        def main():
25434            ˇfor item in items:
25435                ˇwhile item.active:
25436                    ˇif item.value > 10:
25437                        ˇcontinue
25438                    ˇelif item.value < 0:
25439                        ˇbreak
25440                    ˇelse:
25441                        ˇwith item.context() as ctx:
25442                            ˇyield count
25443                ˇelse:
25444                    ˇlog('while else')
25445            ˇelse:
25446                ˇlog('for else')
25447    "});
25448    // test relative indent is preserved when tab
25449    // for `if`, `elif`, `else`, `while`, `with` and `for`
25450    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25451    cx.assert_editor_state(indoc! {"
25452        def main():
25453                ˇfor item in items:
25454                    ˇwhile item.active:
25455                        ˇif item.value > 10:
25456                            ˇcontinue
25457                        ˇelif item.value < 0:
25458                            ˇbreak
25459                        ˇelse:
25460                            ˇwith item.context() as ctx:
25461                                ˇyield count
25462                    ˇelse:
25463                        ˇlog('while else')
25464                ˇelse:
25465                    ˇlog('for else')
25466    "});
25467
25468    // test cursor move to start of each line on tab
25469    // for `try`, `except`, `else`, `finally`, `match` and `def`
25470    cx.set_state(indoc! {"
25471        def main():
25472        ˇ    try:
25473        ˇ        fetch()
25474        ˇ    except ValueError:
25475        ˇ        handle_error()
25476        ˇ    else:
25477        ˇ        match value:
25478        ˇ            case _:
25479        ˇ    finally:
25480        ˇ        def status():
25481        ˇ            return 0
25482    "});
25483    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25484    cx.assert_editor_state(indoc! {"
25485        def main():
25486            ˇtry:
25487                ˇfetch()
25488            ˇexcept ValueError:
25489                ˇhandle_error()
25490            ˇelse:
25491                ˇmatch value:
25492                    ˇcase _:
25493            ˇfinally:
25494                ˇdef status():
25495                    ˇreturn 0
25496    "});
25497    // test relative indent is preserved when tab
25498    // for `try`, `except`, `else`, `finally`, `match` and `def`
25499    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25500    cx.assert_editor_state(indoc! {"
25501        def main():
25502                ˇtry:
25503                    ˇfetch()
25504                ˇexcept ValueError:
25505                    ˇhandle_error()
25506                ˇelse:
25507                    ˇmatch value:
25508                        ˇcase _:
25509                ˇfinally:
25510                    ˇdef status():
25511                        ˇreturn 0
25512    "});
25513}
25514
25515#[gpui::test]
25516async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25517    init_test(cx, |_| {});
25518
25519    let mut cx = EditorTestContext::new(cx).await;
25520    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25521    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25522
25523    // test `else` auto outdents when typed inside `if` block
25524    cx.set_state(indoc! {"
25525        def main():
25526            if i == 2:
25527                return
25528                ˇ
25529    "});
25530    cx.update_editor(|editor, window, cx| {
25531        editor.handle_input("else:", window, cx);
25532    });
25533    cx.assert_editor_state(indoc! {"
25534        def main():
25535            if i == 2:
25536                return
25537            else:ˇ
25538    "});
25539
25540    // test `except` auto outdents when typed inside `try` block
25541    cx.set_state(indoc! {"
25542        def main():
25543            try:
25544                i = 2
25545                ˇ
25546    "});
25547    cx.update_editor(|editor, window, cx| {
25548        editor.handle_input("except:", window, cx);
25549    });
25550    cx.assert_editor_state(indoc! {"
25551        def main():
25552            try:
25553                i = 2
25554            except:ˇ
25555    "});
25556
25557    // test `else` auto outdents when typed inside `except` block
25558    cx.set_state(indoc! {"
25559        def main():
25560            try:
25561                i = 2
25562            except:
25563                j = 2
25564                ˇ
25565    "});
25566    cx.update_editor(|editor, window, cx| {
25567        editor.handle_input("else:", window, cx);
25568    });
25569    cx.assert_editor_state(indoc! {"
25570        def main():
25571            try:
25572                i = 2
25573            except:
25574                j = 2
25575            else:ˇ
25576    "});
25577
25578    // test `finally` auto outdents when typed inside `else` block
25579    cx.set_state(indoc! {"
25580        def main():
25581            try:
25582                i = 2
25583            except:
25584                j = 2
25585            else:
25586                k = 2
25587                ˇ
25588    "});
25589    cx.update_editor(|editor, window, cx| {
25590        editor.handle_input("finally:", window, cx);
25591    });
25592    cx.assert_editor_state(indoc! {"
25593        def main():
25594            try:
25595                i = 2
25596            except:
25597                j = 2
25598            else:
25599                k = 2
25600            finally:ˇ
25601    "});
25602
25603    // test `else` does not outdents when typed inside `except` block right after for block
25604    cx.set_state(indoc! {"
25605        def main():
25606            try:
25607                i = 2
25608            except:
25609                for i in range(n):
25610                    pass
25611                ˇ
25612    "});
25613    cx.update_editor(|editor, window, cx| {
25614        editor.handle_input("else:", window, cx);
25615    });
25616    cx.assert_editor_state(indoc! {"
25617        def main():
25618            try:
25619                i = 2
25620            except:
25621                for i in range(n):
25622                    pass
25623                else:ˇ
25624    "});
25625
25626    // test `finally` auto outdents when typed inside `else` block right after for block
25627    cx.set_state(indoc! {"
25628        def main():
25629            try:
25630                i = 2
25631            except:
25632                j = 2
25633            else:
25634                for i in range(n):
25635                    pass
25636                ˇ
25637    "});
25638    cx.update_editor(|editor, window, cx| {
25639        editor.handle_input("finally:", window, cx);
25640    });
25641    cx.assert_editor_state(indoc! {"
25642        def main():
25643            try:
25644                i = 2
25645            except:
25646                j = 2
25647            else:
25648                for i in range(n):
25649                    pass
25650            finally:ˇ
25651    "});
25652
25653    // test `except` outdents to inner "try" block
25654    cx.set_state(indoc! {"
25655        def main():
25656            try:
25657                i = 2
25658                if i == 2:
25659                    try:
25660                        i = 3
25661                        ˇ
25662    "});
25663    cx.update_editor(|editor, window, cx| {
25664        editor.handle_input("except:", window, cx);
25665    });
25666    cx.assert_editor_state(indoc! {"
25667        def main():
25668            try:
25669                i = 2
25670                if i == 2:
25671                    try:
25672                        i = 3
25673                    except:ˇ
25674    "});
25675
25676    // test `except` outdents to outer "try" block
25677    cx.set_state(indoc! {"
25678        def main():
25679            try:
25680                i = 2
25681                if i == 2:
25682                    try:
25683                        i = 3
25684                ˇ
25685    "});
25686    cx.update_editor(|editor, window, cx| {
25687        editor.handle_input("except:", window, cx);
25688    });
25689    cx.assert_editor_state(indoc! {"
25690        def main():
25691            try:
25692                i = 2
25693                if i == 2:
25694                    try:
25695                        i = 3
25696            except:ˇ
25697    "});
25698
25699    // test `else` stays at correct indent when typed after `for` block
25700    cx.set_state(indoc! {"
25701        def main():
25702            for i in range(10):
25703                if i == 3:
25704                    break
25705            ˇ
25706    "});
25707    cx.update_editor(|editor, window, cx| {
25708        editor.handle_input("else:", window, cx);
25709    });
25710    cx.assert_editor_state(indoc! {"
25711        def main():
25712            for i in range(10):
25713                if i == 3:
25714                    break
25715            else:ˇ
25716    "});
25717
25718    // test does not outdent on typing after line with square brackets
25719    cx.set_state(indoc! {"
25720        def f() -> list[str]:
25721            ˇ
25722    "});
25723    cx.update_editor(|editor, window, cx| {
25724        editor.handle_input("a", window, cx);
25725    });
25726    cx.assert_editor_state(indoc! {"
25727        def f() -> list[str]:
2572825729    "});
25730
25731    // test does not outdent on typing : after case keyword
25732    cx.set_state(indoc! {"
25733        match 1:
25734            caseˇ
25735    "});
25736    cx.update_editor(|editor, window, cx| {
25737        editor.handle_input(":", window, cx);
25738    });
25739    cx.assert_editor_state(indoc! {"
25740        match 1:
25741            case:ˇ
25742    "});
25743}
25744
25745#[gpui::test]
25746async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25747    init_test(cx, |_| {});
25748    update_test_language_settings(cx, |settings| {
25749        settings.defaults.extend_comment_on_newline = Some(false);
25750    });
25751    let mut cx = EditorTestContext::new(cx).await;
25752    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25753    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25754
25755    // test correct indent after newline on comment
25756    cx.set_state(indoc! {"
25757        # COMMENT:ˇ
25758    "});
25759    cx.update_editor(|editor, window, cx| {
25760        editor.newline(&Newline, window, cx);
25761    });
25762    cx.assert_editor_state(indoc! {"
25763        # COMMENT:
25764        ˇ
25765    "});
25766
25767    // test correct indent after newline in brackets
25768    cx.set_state(indoc! {"
25769        {ˇ}
25770    "});
25771    cx.update_editor(|editor, window, cx| {
25772        editor.newline(&Newline, window, cx);
25773    });
25774    cx.run_until_parked();
25775    cx.assert_editor_state(indoc! {"
25776        {
25777            ˇ
25778        }
25779    "});
25780
25781    cx.set_state(indoc! {"
25782        (ˇ)
25783    "});
25784    cx.update_editor(|editor, window, cx| {
25785        editor.newline(&Newline, window, cx);
25786    });
25787    cx.run_until_parked();
25788    cx.assert_editor_state(indoc! {"
25789        (
25790            ˇ
25791        )
25792    "});
25793
25794    // do not indent after empty lists or dictionaries
25795    cx.set_state(indoc! {"
25796        a = []ˇ
25797    "});
25798    cx.update_editor(|editor, window, cx| {
25799        editor.newline(&Newline, window, cx);
25800    });
25801    cx.run_until_parked();
25802    cx.assert_editor_state(indoc! {"
25803        a = []
25804        ˇ
25805    "});
25806}
25807
25808#[gpui::test]
25809async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25810    init_test(cx, |_| {});
25811
25812    let mut cx = EditorTestContext::new(cx).await;
25813    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25814    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25815
25816    // test cursor move to start of each line on tab
25817    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25818    cx.set_state(indoc! {"
25819        function main() {
25820        ˇ    for item in $items; do
25821        ˇ        while [ -n \"$item\" ]; do
25822        ˇ            if [ \"$value\" -gt 10 ]; then
25823        ˇ                continue
25824        ˇ            elif [ \"$value\" -lt 0 ]; then
25825        ˇ                break
25826        ˇ            else
25827        ˇ                echo \"$item\"
25828        ˇ            fi
25829        ˇ        done
25830        ˇ    done
25831        ˇ}
25832    "});
25833    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25834    cx.assert_editor_state(indoc! {"
25835        function main() {
25836            ˇfor item in $items; do
25837                ˇwhile [ -n \"$item\" ]; do
25838                    ˇif [ \"$value\" -gt 10 ]; then
25839                        ˇcontinue
25840                    ˇelif [ \"$value\" -lt 0 ]; then
25841                        ˇbreak
25842                    ˇelse
25843                        ˇecho \"$item\"
25844                    ˇfi
25845                ˇdone
25846            ˇdone
25847        ˇ}
25848    "});
25849    // test relative indent is preserved when tab
25850    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25851    cx.assert_editor_state(indoc! {"
25852        function main() {
25853                ˇfor item in $items; do
25854                    ˇwhile [ -n \"$item\" ]; do
25855                        ˇif [ \"$value\" -gt 10 ]; then
25856                            ˇcontinue
25857                        ˇelif [ \"$value\" -lt 0 ]; then
25858                            ˇbreak
25859                        ˇelse
25860                            ˇecho \"$item\"
25861                        ˇfi
25862                    ˇdone
25863                ˇdone
25864            ˇ}
25865    "});
25866
25867    // test cursor move to start of each line on tab
25868    // for `case` statement with patterns
25869    cx.set_state(indoc! {"
25870        function handle() {
25871        ˇ    case \"$1\" in
25872        ˇ        start)
25873        ˇ            echo \"a\"
25874        ˇ            ;;
25875        ˇ        stop)
25876        ˇ            echo \"b\"
25877        ˇ            ;;
25878        ˇ        *)
25879        ˇ            echo \"c\"
25880        ˇ            ;;
25881        ˇ    esac
25882        ˇ}
25883    "});
25884    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25885    cx.assert_editor_state(indoc! {"
25886        function handle() {
25887            ˇcase \"$1\" in
25888                ˇstart)
25889                    ˇecho \"a\"
25890                    ˇ;;
25891                ˇstop)
25892                    ˇecho \"b\"
25893                    ˇ;;
25894                ˇ*)
25895                    ˇecho \"c\"
25896                    ˇ;;
25897            ˇesac
25898        ˇ}
25899    "});
25900}
25901
25902#[gpui::test]
25903async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25904    init_test(cx, |_| {});
25905
25906    let mut cx = EditorTestContext::new(cx).await;
25907    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25908    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25909
25910    // test indents on comment insert
25911    cx.set_state(indoc! {"
25912        function main() {
25913        ˇ    for item in $items; do
25914        ˇ        while [ -n \"$item\" ]; do
25915        ˇ            if [ \"$value\" -gt 10 ]; then
25916        ˇ                continue
25917        ˇ            elif [ \"$value\" -lt 0 ]; then
25918        ˇ                break
25919        ˇ            else
25920        ˇ                echo \"$item\"
25921        ˇ            fi
25922        ˇ        done
25923        ˇ    done
25924        ˇ}
25925    "});
25926    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25927    cx.assert_editor_state(indoc! {"
25928        function main() {
25929        #ˇ    for item in $items; do
25930        #ˇ        while [ -n \"$item\" ]; do
25931        #ˇ            if [ \"$value\" -gt 10 ]; then
25932        #ˇ                continue
25933        #ˇ            elif [ \"$value\" -lt 0 ]; then
25934        #ˇ                break
25935        #ˇ            else
25936        #ˇ                echo \"$item\"
25937        #ˇ            fi
25938        #ˇ        done
25939        #ˇ    done
25940        #ˇ}
25941    "});
25942}
25943
25944#[gpui::test]
25945async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25946    init_test(cx, |_| {});
25947
25948    let mut cx = EditorTestContext::new(cx).await;
25949    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25950    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25951
25952    // test `else` auto outdents when typed inside `if` block
25953    cx.set_state(indoc! {"
25954        if [ \"$1\" = \"test\" ]; then
25955            echo \"foo bar\"
25956            ˇ
25957    "});
25958    cx.update_editor(|editor, window, cx| {
25959        editor.handle_input("else", window, cx);
25960    });
25961    cx.assert_editor_state(indoc! {"
25962        if [ \"$1\" = \"test\" ]; then
25963            echo \"foo bar\"
25964        elseˇ
25965    "});
25966
25967    // test `elif` auto outdents when typed inside `if` block
25968    cx.set_state(indoc! {"
25969        if [ \"$1\" = \"test\" ]; then
25970            echo \"foo bar\"
25971            ˇ
25972    "});
25973    cx.update_editor(|editor, window, cx| {
25974        editor.handle_input("elif", window, cx);
25975    });
25976    cx.assert_editor_state(indoc! {"
25977        if [ \"$1\" = \"test\" ]; then
25978            echo \"foo bar\"
25979        elifˇ
25980    "});
25981
25982    // test `fi` auto outdents when typed inside `else` block
25983    cx.set_state(indoc! {"
25984        if [ \"$1\" = \"test\" ]; then
25985            echo \"foo bar\"
25986        else
25987            echo \"bar baz\"
25988            ˇ
25989    "});
25990    cx.update_editor(|editor, window, cx| {
25991        editor.handle_input("fi", window, cx);
25992    });
25993    cx.assert_editor_state(indoc! {"
25994        if [ \"$1\" = \"test\" ]; then
25995            echo \"foo bar\"
25996        else
25997            echo \"bar baz\"
25998        fiˇ
25999    "});
26000
26001    // test `done` auto outdents when typed inside `while` block
26002    cx.set_state(indoc! {"
26003        while read line; do
26004            echo \"$line\"
26005            ˇ
26006    "});
26007    cx.update_editor(|editor, window, cx| {
26008        editor.handle_input("done", window, cx);
26009    });
26010    cx.assert_editor_state(indoc! {"
26011        while read line; do
26012            echo \"$line\"
26013        doneˇ
26014    "});
26015
26016    // test `done` auto outdents when typed inside `for` block
26017    cx.set_state(indoc! {"
26018        for file in *.txt; do
26019            cat \"$file\"
26020            ˇ
26021    "});
26022    cx.update_editor(|editor, window, cx| {
26023        editor.handle_input("done", window, cx);
26024    });
26025    cx.assert_editor_state(indoc! {"
26026        for file in *.txt; do
26027            cat \"$file\"
26028        doneˇ
26029    "});
26030
26031    // test `esac` auto outdents when typed inside `case` block
26032    cx.set_state(indoc! {"
26033        case \"$1\" in
26034            start)
26035                echo \"foo bar\"
26036                ;;
26037            stop)
26038                echo \"bar baz\"
26039                ;;
26040            ˇ
26041    "});
26042    cx.update_editor(|editor, window, cx| {
26043        editor.handle_input("esac", window, cx);
26044    });
26045    cx.assert_editor_state(indoc! {"
26046        case \"$1\" in
26047            start)
26048                echo \"foo bar\"
26049                ;;
26050            stop)
26051                echo \"bar baz\"
26052                ;;
26053        esacˇ
26054    "});
26055
26056    // test `*)` auto outdents when typed inside `case` block
26057    cx.set_state(indoc! {"
26058        case \"$1\" in
26059            start)
26060                echo \"foo bar\"
26061                ;;
26062                ˇ
26063    "});
26064    cx.update_editor(|editor, window, cx| {
26065        editor.handle_input("*)", window, cx);
26066    });
26067    cx.assert_editor_state(indoc! {"
26068        case \"$1\" in
26069            start)
26070                echo \"foo bar\"
26071                ;;
26072            *)ˇ
26073    "});
26074
26075    // test `fi` outdents to correct level with nested if blocks
26076    cx.set_state(indoc! {"
26077        if [ \"$1\" = \"test\" ]; then
26078            echo \"outer if\"
26079            if [ \"$2\" = \"debug\" ]; then
26080                echo \"inner if\"
26081                ˇ
26082    "});
26083    cx.update_editor(|editor, window, cx| {
26084        editor.handle_input("fi", window, cx);
26085    });
26086    cx.assert_editor_state(indoc! {"
26087        if [ \"$1\" = \"test\" ]; then
26088            echo \"outer if\"
26089            if [ \"$2\" = \"debug\" ]; then
26090                echo \"inner if\"
26091            fiˇ
26092    "});
26093}
26094
26095#[gpui::test]
26096async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
26097    init_test(cx, |_| {});
26098    update_test_language_settings(cx, |settings| {
26099        settings.defaults.extend_comment_on_newline = Some(false);
26100    });
26101    let mut cx = EditorTestContext::new(cx).await;
26102    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
26103    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
26104
26105    // test correct indent after newline on comment
26106    cx.set_state(indoc! {"
26107        # COMMENT:ˇ
26108    "});
26109    cx.update_editor(|editor, window, cx| {
26110        editor.newline(&Newline, window, cx);
26111    });
26112    cx.assert_editor_state(indoc! {"
26113        # COMMENT:
26114        ˇ
26115    "});
26116
26117    // test correct indent after newline after `then`
26118    cx.set_state(indoc! {"
26119
26120        if [ \"$1\" = \"test\" ]; thenˇ
26121    "});
26122    cx.update_editor(|editor, window, cx| {
26123        editor.newline(&Newline, window, cx);
26124    });
26125    cx.run_until_parked();
26126    cx.assert_editor_state(indoc! {"
26127
26128        if [ \"$1\" = \"test\" ]; then
26129            ˇ
26130    "});
26131
26132    // test correct indent after newline after `else`
26133    cx.set_state(indoc! {"
26134        if [ \"$1\" = \"test\" ]; then
26135        elseˇ
26136    "});
26137    cx.update_editor(|editor, window, cx| {
26138        editor.newline(&Newline, window, cx);
26139    });
26140    cx.run_until_parked();
26141    cx.assert_editor_state(indoc! {"
26142        if [ \"$1\" = \"test\" ]; then
26143        else
26144            ˇ
26145    "});
26146
26147    // test correct indent after newline after `elif`
26148    cx.set_state(indoc! {"
26149        if [ \"$1\" = \"test\" ]; then
26150        elifˇ
26151    "});
26152    cx.update_editor(|editor, window, cx| {
26153        editor.newline(&Newline, window, cx);
26154    });
26155    cx.run_until_parked();
26156    cx.assert_editor_state(indoc! {"
26157        if [ \"$1\" = \"test\" ]; then
26158        elif
26159            ˇ
26160    "});
26161
26162    // test correct indent after newline after `do`
26163    cx.set_state(indoc! {"
26164        for file in *.txt; doˇ
26165    "});
26166    cx.update_editor(|editor, window, cx| {
26167        editor.newline(&Newline, window, cx);
26168    });
26169    cx.run_until_parked();
26170    cx.assert_editor_state(indoc! {"
26171        for file in *.txt; do
26172            ˇ
26173    "});
26174
26175    // test correct indent after newline after case pattern
26176    cx.set_state(indoc! {"
26177        case \"$1\" in
26178            start)ˇ
26179    "});
26180    cx.update_editor(|editor, window, cx| {
26181        editor.newline(&Newline, window, cx);
26182    });
26183    cx.run_until_parked();
26184    cx.assert_editor_state(indoc! {"
26185        case \"$1\" in
26186            start)
26187                ˇ
26188    "});
26189
26190    // test correct indent after newline after case pattern
26191    cx.set_state(indoc! {"
26192        case \"$1\" in
26193            start)
26194                ;;
26195            *)ˇ
26196    "});
26197    cx.update_editor(|editor, window, cx| {
26198        editor.newline(&Newline, window, cx);
26199    });
26200    cx.run_until_parked();
26201    cx.assert_editor_state(indoc! {"
26202        case \"$1\" in
26203            start)
26204                ;;
26205            *)
26206                ˇ
26207    "});
26208
26209    // test correct indent after newline after function opening brace
26210    cx.set_state(indoc! {"
26211        function test() {ˇ}
26212    "});
26213    cx.update_editor(|editor, window, cx| {
26214        editor.newline(&Newline, window, cx);
26215    });
26216    cx.run_until_parked();
26217    cx.assert_editor_state(indoc! {"
26218        function test() {
26219            ˇ
26220        }
26221    "});
26222
26223    // test no extra indent after semicolon on same line
26224    cx.set_state(indoc! {"
26225        echo \"test\"26226    "});
26227    cx.update_editor(|editor, window, cx| {
26228        editor.newline(&Newline, window, cx);
26229    });
26230    cx.run_until_parked();
26231    cx.assert_editor_state(indoc! {"
26232        echo \"test\";
26233        ˇ
26234    "});
26235}
26236
26237fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26238    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26239    point..point
26240}
26241
26242#[track_caller]
26243fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26244    let (text, ranges) = marked_text_ranges(marked_text, true);
26245    assert_eq!(editor.text(cx), text);
26246    assert_eq!(
26247        editor.selections.ranges(&editor.display_snapshot(cx)),
26248        ranges
26249            .iter()
26250            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26251            .collect::<Vec<_>>(),
26252        "Assert selections are {}",
26253        marked_text
26254    );
26255}
26256
26257pub fn handle_signature_help_request(
26258    cx: &mut EditorLspTestContext,
26259    mocked_response: lsp::SignatureHelp,
26260) -> impl Future<Output = ()> + use<> {
26261    let mut request =
26262        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26263            let mocked_response = mocked_response.clone();
26264            async move { Ok(Some(mocked_response)) }
26265        });
26266
26267    async move {
26268        request.next().await;
26269    }
26270}
26271
26272#[track_caller]
26273pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26274    cx.update_editor(|editor, _, _| {
26275        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26276            let entries = menu.entries.borrow();
26277            let entries = entries
26278                .iter()
26279                .map(|entry| entry.string.as_str())
26280                .collect::<Vec<_>>();
26281            assert_eq!(entries, expected);
26282        } else {
26283            panic!("Expected completions menu");
26284        }
26285    });
26286}
26287
26288#[gpui::test]
26289async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26290    init_test(cx, |_| {});
26291    let mut cx = EditorLspTestContext::new_rust(
26292        lsp::ServerCapabilities {
26293            completion_provider: Some(lsp::CompletionOptions {
26294                ..Default::default()
26295            }),
26296            ..Default::default()
26297        },
26298        cx,
26299    )
26300    .await;
26301    cx.lsp
26302        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26303            Ok(Some(lsp::CompletionResponse::Array(vec![
26304                lsp::CompletionItem {
26305                    label: "unsafe".into(),
26306                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26307                        range: lsp::Range {
26308                            start: lsp::Position {
26309                                line: 0,
26310                                character: 9,
26311                            },
26312                            end: lsp::Position {
26313                                line: 0,
26314                                character: 11,
26315                            },
26316                        },
26317                        new_text: "unsafe".to_string(),
26318                    })),
26319                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26320                    ..Default::default()
26321                },
26322            ])))
26323        });
26324
26325    cx.update_editor(|editor, _, cx| {
26326        editor.project().unwrap().update(cx, |project, cx| {
26327            project.snippets().update(cx, |snippets, _cx| {
26328                snippets.add_snippet_for_test(
26329                    None,
26330                    PathBuf::from("test_snippets.json"),
26331                    vec![
26332                        Arc::new(project::snippet_provider::Snippet {
26333                            prefix: vec![
26334                                "unlimited word count".to_string(),
26335                                "unlimit word count".to_string(),
26336                                "unlimited unknown".to_string(),
26337                            ],
26338                            body: "this is many words".to_string(),
26339                            description: Some("description".to_string()),
26340                            name: "multi-word snippet test".to_string(),
26341                        }),
26342                        Arc::new(project::snippet_provider::Snippet {
26343                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26344                            body: "fewer words".to_string(),
26345                            description: Some("alt description".to_string()),
26346                            name: "other name".to_string(),
26347                        }),
26348                        Arc::new(project::snippet_provider::Snippet {
26349                            prefix: vec!["ab aa".to_string()],
26350                            body: "abcd".to_string(),
26351                            description: None,
26352                            name: "alphabet".to_string(),
26353                        }),
26354                    ],
26355                );
26356            });
26357        })
26358    });
26359
26360    let get_completions = |cx: &mut EditorLspTestContext| {
26361        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26362            Some(CodeContextMenu::Completions(context_menu)) => {
26363                let entries = context_menu.entries.borrow();
26364                entries
26365                    .iter()
26366                    .map(|entry| entry.string.clone())
26367                    .collect_vec()
26368            }
26369            _ => vec![],
26370        })
26371    };
26372
26373    // snippets:
26374    //  @foo
26375    //  foo bar
26376    //
26377    // when typing:
26378    //
26379    // when typing:
26380    //  - if I type a symbol "open the completions with snippets only"
26381    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26382    //
26383    // stuff we need:
26384    //  - filtering logic change?
26385    //  - remember how far back the completion started.
26386
26387    let test_cases: &[(&str, &[&str])] = &[
26388        (
26389            "un",
26390            &[
26391                "unsafe",
26392                "unlimit word count",
26393                "unlimited unknown",
26394                "unlimited word count",
26395                "unsnip",
26396            ],
26397        ),
26398        (
26399            "u ",
26400            &[
26401                "unlimit word count",
26402                "unlimited unknown",
26403                "unlimited word count",
26404            ],
26405        ),
26406        ("u a", &["ab aa", "unsafe"]), // unsAfe
26407        (
26408            "u u",
26409            &[
26410                "unsafe",
26411                "unlimit word count",
26412                "unlimited unknown", // ranked highest among snippets
26413                "unlimited word count",
26414                "unsnip",
26415            ],
26416        ),
26417        ("uw c", &["unlimit word count", "unlimited word count"]),
26418        (
26419            "u w",
26420            &[
26421                "unlimit word count",
26422                "unlimited word count",
26423                "unlimited unknown",
26424            ],
26425        ),
26426        ("u w ", &["unlimit word count", "unlimited word count"]),
26427        (
26428            "u ",
26429            &[
26430                "unlimit word count",
26431                "unlimited unknown",
26432                "unlimited word count",
26433            ],
26434        ),
26435        ("wor", &[]),
26436        ("uf", &["unsafe"]),
26437        ("af", &["unsafe"]),
26438        ("afu", &[]),
26439        (
26440            "ue",
26441            &["unsafe", "unlimited unknown", "unlimited word count"],
26442        ),
26443        ("@", &["@few"]),
26444        ("@few", &["@few"]),
26445        ("@ ", &[]),
26446        ("a@", &["@few"]),
26447        ("a@f", &["@few", "unsafe"]),
26448        ("a@fw", &["@few"]),
26449        ("a", &["ab aa", "unsafe"]),
26450        ("aa", &["ab aa"]),
26451        ("aaa", &["ab aa"]),
26452        ("ab", &["ab aa"]),
26453        ("ab ", &["ab aa"]),
26454        ("ab a", &["ab aa", "unsafe"]),
26455        ("ab ab", &["ab aa"]),
26456        ("ab ab aa", &["ab aa"]),
26457    ];
26458
26459    for &(input_to_simulate, expected_completions) in test_cases {
26460        cx.set_state("fn a() { ˇ }\n");
26461        for c in input_to_simulate.split("") {
26462            cx.simulate_input(c);
26463            cx.run_until_parked();
26464        }
26465        let expected_completions = expected_completions
26466            .iter()
26467            .map(|s| s.to_string())
26468            .collect_vec();
26469        assert_eq!(
26470            get_completions(&mut cx),
26471            expected_completions,
26472            "< actual / expected >, input = {input_to_simulate:?}",
26473        );
26474    }
26475}
26476
26477/// Handle completion request passing a marked string specifying where the completion
26478/// should be triggered from using '|' character, what range should be replaced, and what completions
26479/// should be returned using '<' and '>' to delimit the range.
26480///
26481/// Also see `handle_completion_request_with_insert_and_replace`.
26482#[track_caller]
26483pub fn handle_completion_request(
26484    marked_string: &str,
26485    completions: Vec<&'static str>,
26486    is_incomplete: bool,
26487    counter: Arc<AtomicUsize>,
26488    cx: &mut EditorLspTestContext,
26489) -> impl Future<Output = ()> {
26490    let complete_from_marker: TextRangeMarker = '|'.into();
26491    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26492    let (_, mut marked_ranges) = marked_text_ranges_by(
26493        marked_string,
26494        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26495    );
26496
26497    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26498        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26499    ));
26500    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26501    let replace_range =
26502        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26503
26504    let mut request =
26505        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26506            let completions = completions.clone();
26507            counter.fetch_add(1, atomic::Ordering::Release);
26508            async move {
26509                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26510                assert_eq!(
26511                    params.text_document_position.position,
26512                    complete_from_position
26513                );
26514                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26515                    is_incomplete,
26516                    item_defaults: None,
26517                    items: completions
26518                        .iter()
26519                        .map(|completion_text| lsp::CompletionItem {
26520                            label: completion_text.to_string(),
26521                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26522                                range: replace_range,
26523                                new_text: completion_text.to_string(),
26524                            })),
26525                            ..Default::default()
26526                        })
26527                        .collect(),
26528                })))
26529            }
26530        });
26531
26532    async move {
26533        request.next().await;
26534    }
26535}
26536
26537/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26538/// given instead, which also contains an `insert` range.
26539///
26540/// This function uses markers to define ranges:
26541/// - `|` marks the cursor position
26542/// - `<>` marks the replace range
26543/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26544pub fn handle_completion_request_with_insert_and_replace(
26545    cx: &mut EditorLspTestContext,
26546    marked_string: &str,
26547    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26548    counter: Arc<AtomicUsize>,
26549) -> impl Future<Output = ()> {
26550    let complete_from_marker: TextRangeMarker = '|'.into();
26551    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26552    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26553
26554    let (_, mut marked_ranges) = marked_text_ranges_by(
26555        marked_string,
26556        vec![
26557            complete_from_marker.clone(),
26558            replace_range_marker.clone(),
26559            insert_range_marker.clone(),
26560        ],
26561    );
26562
26563    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26564        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26565    ));
26566    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26567    let replace_range =
26568        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26569
26570    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26571        Some(ranges) if !ranges.is_empty() => {
26572            let range1 = ranges[0].clone();
26573            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26574        }
26575        _ => lsp::Range {
26576            start: replace_range.start,
26577            end: complete_from_position,
26578        },
26579    };
26580
26581    let mut request =
26582        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26583            let completions = completions.clone();
26584            counter.fetch_add(1, atomic::Ordering::Release);
26585            async move {
26586                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26587                assert_eq!(
26588                    params.text_document_position.position, complete_from_position,
26589                    "marker `|` position doesn't match",
26590                );
26591                Ok(Some(lsp::CompletionResponse::Array(
26592                    completions
26593                        .iter()
26594                        .map(|(label, new_text)| lsp::CompletionItem {
26595                            label: label.to_string(),
26596                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26597                                lsp::InsertReplaceEdit {
26598                                    insert: insert_range,
26599                                    replace: replace_range,
26600                                    new_text: new_text.to_string(),
26601                                },
26602                            )),
26603                            ..Default::default()
26604                        })
26605                        .collect(),
26606                )))
26607            }
26608        });
26609
26610    async move {
26611        request.next().await;
26612    }
26613}
26614
26615fn handle_resolve_completion_request(
26616    cx: &mut EditorLspTestContext,
26617    edits: Option<Vec<(&'static str, &'static str)>>,
26618) -> impl Future<Output = ()> {
26619    let edits = edits.map(|edits| {
26620        edits
26621            .iter()
26622            .map(|(marked_string, new_text)| {
26623                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26624                let replace_range = cx.to_lsp_range(
26625                    MultiBufferOffset(marked_ranges[0].start)
26626                        ..MultiBufferOffset(marked_ranges[0].end),
26627                );
26628                lsp::TextEdit::new(replace_range, new_text.to_string())
26629            })
26630            .collect::<Vec<_>>()
26631    });
26632
26633    let mut request =
26634        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26635            let edits = edits.clone();
26636            async move {
26637                Ok(lsp::CompletionItem {
26638                    additional_text_edits: edits,
26639                    ..Default::default()
26640                })
26641            }
26642        });
26643
26644    async move {
26645        request.next().await;
26646    }
26647}
26648
26649pub(crate) fn update_test_language_settings(
26650    cx: &mut TestAppContext,
26651    f: impl Fn(&mut AllLanguageSettingsContent),
26652) {
26653    cx.update(|cx| {
26654        SettingsStore::update_global(cx, |store, cx| {
26655            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26656        });
26657    });
26658}
26659
26660pub(crate) fn update_test_project_settings(
26661    cx: &mut TestAppContext,
26662    f: impl Fn(&mut ProjectSettingsContent),
26663) {
26664    cx.update(|cx| {
26665        SettingsStore::update_global(cx, |store, cx| {
26666            store.update_user_settings(cx, |settings| f(&mut settings.project));
26667        });
26668    });
26669}
26670
26671pub(crate) fn update_test_editor_settings(
26672    cx: &mut TestAppContext,
26673    f: impl Fn(&mut EditorSettingsContent),
26674) {
26675    cx.update(|cx| {
26676        SettingsStore::update_global(cx, |store, cx| {
26677            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26678        })
26679    })
26680}
26681
26682pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26683    cx.update(|cx| {
26684        assets::Assets.load_test_fonts(cx);
26685        let store = SettingsStore::test(cx);
26686        cx.set_global(store);
26687        theme::init(theme::LoadThemes::JustBase, cx);
26688        release_channel::init(semver::Version::new(0, 0, 0), cx);
26689        crate::init(cx);
26690    });
26691    zlog::init_test();
26692    update_test_language_settings(cx, f);
26693}
26694
26695#[track_caller]
26696fn assert_hunk_revert(
26697    not_reverted_text_with_selections: &str,
26698    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26699    expected_reverted_text_with_selections: &str,
26700    base_text: &str,
26701    cx: &mut EditorLspTestContext,
26702) {
26703    cx.set_state(not_reverted_text_with_selections);
26704    cx.set_head_text(base_text);
26705    cx.executor().run_until_parked();
26706
26707    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26708        let snapshot = editor.snapshot(window, cx);
26709        let reverted_hunk_statuses = snapshot
26710            .buffer_snapshot()
26711            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26712            .map(|hunk| hunk.status().kind)
26713            .collect::<Vec<_>>();
26714
26715        editor.git_restore(&Default::default(), window, cx);
26716        reverted_hunk_statuses
26717    });
26718    cx.executor().run_until_parked();
26719    cx.assert_editor_state(expected_reverted_text_with_selections);
26720    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26721}
26722
26723#[gpui::test(iterations = 10)]
26724async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26725    init_test(cx, |_| {});
26726
26727    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26728    let counter = diagnostic_requests.clone();
26729
26730    let fs = FakeFs::new(cx.executor());
26731    fs.insert_tree(
26732        path!("/a"),
26733        json!({
26734            "first.rs": "fn main() { let a = 5; }",
26735            "second.rs": "// Test file",
26736        }),
26737    )
26738    .await;
26739
26740    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26741    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26742    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26743
26744    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26745    language_registry.add(rust_lang());
26746    let mut fake_servers = language_registry.register_fake_lsp(
26747        "Rust",
26748        FakeLspAdapter {
26749            capabilities: lsp::ServerCapabilities {
26750                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26751                    lsp::DiagnosticOptions {
26752                        identifier: None,
26753                        inter_file_dependencies: true,
26754                        workspace_diagnostics: true,
26755                        work_done_progress_options: Default::default(),
26756                    },
26757                )),
26758                ..Default::default()
26759            },
26760            ..Default::default()
26761        },
26762    );
26763
26764    let editor = workspace
26765        .update(cx, |workspace, window, cx| {
26766            workspace.open_abs_path(
26767                PathBuf::from(path!("/a/first.rs")),
26768                OpenOptions::default(),
26769                window,
26770                cx,
26771            )
26772        })
26773        .unwrap()
26774        .await
26775        .unwrap()
26776        .downcast::<Editor>()
26777        .unwrap();
26778    let fake_server = fake_servers.next().await.unwrap();
26779    let server_id = fake_server.server.server_id();
26780    let mut first_request = fake_server
26781        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26782            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26783            let result_id = Some(new_result_id.to_string());
26784            assert_eq!(
26785                params.text_document.uri,
26786                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26787            );
26788            async move {
26789                Ok(lsp::DocumentDiagnosticReportResult::Report(
26790                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26791                        related_documents: None,
26792                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26793                            items: Vec::new(),
26794                            result_id,
26795                        },
26796                    }),
26797                ))
26798            }
26799        });
26800
26801    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
26802        project.update(cx, |project, cx| {
26803            let buffer_id = editor
26804                .read(cx)
26805                .buffer()
26806                .read(cx)
26807                .as_singleton()
26808                .expect("created a singleton buffer")
26809                .read(cx)
26810                .remote_id();
26811            let buffer_result_id = project
26812                .lsp_store()
26813                .read(cx)
26814                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
26815            assert_eq!(expected, buffer_result_id);
26816        });
26817    };
26818
26819    ensure_result_id(None, cx);
26820    cx.executor().advance_clock(Duration::from_millis(60));
26821    cx.executor().run_until_parked();
26822    assert_eq!(
26823        diagnostic_requests.load(atomic::Ordering::Acquire),
26824        1,
26825        "Opening file should trigger diagnostic request"
26826    );
26827    first_request
26828        .next()
26829        .await
26830        .expect("should have sent the first diagnostics pull request");
26831    ensure_result_id(Some(SharedString::new("1")), cx);
26832
26833    // Editing should trigger diagnostics
26834    editor.update_in(cx, |editor, window, cx| {
26835        editor.handle_input("2", window, cx)
26836    });
26837    cx.executor().advance_clock(Duration::from_millis(60));
26838    cx.executor().run_until_parked();
26839    assert_eq!(
26840        diagnostic_requests.load(atomic::Ordering::Acquire),
26841        2,
26842        "Editing should trigger diagnostic request"
26843    );
26844    ensure_result_id(Some(SharedString::new("2")), cx);
26845
26846    // Moving cursor should not trigger diagnostic request
26847    editor.update_in(cx, |editor, window, cx| {
26848        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26849            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26850        });
26851    });
26852    cx.executor().advance_clock(Duration::from_millis(60));
26853    cx.executor().run_until_parked();
26854    assert_eq!(
26855        diagnostic_requests.load(atomic::Ordering::Acquire),
26856        2,
26857        "Cursor movement should not trigger diagnostic request"
26858    );
26859    ensure_result_id(Some(SharedString::new("2")), cx);
26860    // Multiple rapid edits should be debounced
26861    for _ in 0..5 {
26862        editor.update_in(cx, |editor, window, cx| {
26863            editor.handle_input("x", window, cx)
26864        });
26865    }
26866    cx.executor().advance_clock(Duration::from_millis(60));
26867    cx.executor().run_until_parked();
26868
26869    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26870    assert!(
26871        final_requests <= 4,
26872        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26873    );
26874    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
26875}
26876
26877#[gpui::test]
26878async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26879    // Regression test for issue #11671
26880    // Previously, adding a cursor after moving multiple cursors would reset
26881    // the cursor count instead of adding to the existing cursors.
26882    init_test(cx, |_| {});
26883    let mut cx = EditorTestContext::new(cx).await;
26884
26885    // Create a simple buffer with cursor at start
26886    cx.set_state(indoc! {"
26887        ˇaaaa
26888        bbbb
26889        cccc
26890        dddd
26891        eeee
26892        ffff
26893        gggg
26894        hhhh"});
26895
26896    // Add 2 cursors below (so we have 3 total)
26897    cx.update_editor(|editor, window, cx| {
26898        editor.add_selection_below(&Default::default(), window, cx);
26899        editor.add_selection_below(&Default::default(), window, cx);
26900    });
26901
26902    // Verify we have 3 cursors
26903    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26904    assert_eq!(
26905        initial_count, 3,
26906        "Should have 3 cursors after adding 2 below"
26907    );
26908
26909    // Move down one line
26910    cx.update_editor(|editor, window, cx| {
26911        editor.move_down(&MoveDown, window, cx);
26912    });
26913
26914    // Add another cursor below
26915    cx.update_editor(|editor, window, cx| {
26916        editor.add_selection_below(&Default::default(), window, cx);
26917    });
26918
26919    // Should now have 4 cursors (3 original + 1 new)
26920    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26921    assert_eq!(
26922        final_count, 4,
26923        "Should have 4 cursors after moving and adding another"
26924    );
26925}
26926
26927#[gpui::test]
26928async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26929    init_test(cx, |_| {});
26930
26931    let mut cx = EditorTestContext::new(cx).await;
26932
26933    cx.set_state(indoc!(
26934        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26935           Second line here"#
26936    ));
26937
26938    cx.update_editor(|editor, window, cx| {
26939        // Enable soft wrapping with a narrow width to force soft wrapping and
26940        // confirm that more than 2 rows are being displayed.
26941        editor.set_wrap_width(Some(100.0.into()), cx);
26942        assert!(editor.display_text(cx).lines().count() > 2);
26943
26944        editor.add_selection_below(
26945            &AddSelectionBelow {
26946                skip_soft_wrap: true,
26947            },
26948            window,
26949            cx,
26950        );
26951
26952        assert_eq!(
26953            display_ranges(editor, cx),
26954            &[
26955                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26956                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26957            ]
26958        );
26959
26960        editor.add_selection_above(
26961            &AddSelectionAbove {
26962                skip_soft_wrap: true,
26963            },
26964            window,
26965            cx,
26966        );
26967
26968        assert_eq!(
26969            display_ranges(editor, cx),
26970            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26971        );
26972
26973        editor.add_selection_below(
26974            &AddSelectionBelow {
26975                skip_soft_wrap: false,
26976            },
26977            window,
26978            cx,
26979        );
26980
26981        assert_eq!(
26982            display_ranges(editor, cx),
26983            &[
26984                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26985                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26986            ]
26987        );
26988
26989        editor.add_selection_above(
26990            &AddSelectionAbove {
26991                skip_soft_wrap: false,
26992            },
26993            window,
26994            cx,
26995        );
26996
26997        assert_eq!(
26998            display_ranges(editor, cx),
26999            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
27000        );
27001    });
27002}
27003
27004#[gpui::test]
27005async fn test_insert_snippet(cx: &mut TestAppContext) {
27006    init_test(cx, |_| {});
27007    let mut cx = EditorTestContext::new(cx).await;
27008
27009    cx.update_editor(|editor, _, cx| {
27010        editor.project().unwrap().update(cx, |project, cx| {
27011            project.snippets().update(cx, |snippets, _cx| {
27012                let snippet = project::snippet_provider::Snippet {
27013                    prefix: vec![], // no prefix needed!
27014                    body: "an Unspecified".to_string(),
27015                    description: Some("shhhh it's a secret".to_string()),
27016                    name: "super secret snippet".to_string(),
27017                };
27018                snippets.add_snippet_for_test(
27019                    None,
27020                    PathBuf::from("test_snippets.json"),
27021                    vec![Arc::new(snippet)],
27022                );
27023
27024                let snippet = project::snippet_provider::Snippet {
27025                    prefix: vec![], // no prefix needed!
27026                    body: " Location".to_string(),
27027                    description: Some("the word 'location'".to_string()),
27028                    name: "location word".to_string(),
27029                };
27030                snippets.add_snippet_for_test(
27031                    Some("Markdown".to_string()),
27032                    PathBuf::from("test_snippets.json"),
27033                    vec![Arc::new(snippet)],
27034                );
27035            });
27036        })
27037    });
27038
27039    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
27040
27041    cx.update_editor(|editor, window, cx| {
27042        editor.insert_snippet_at_selections(
27043            &InsertSnippet {
27044                language: None,
27045                name: Some("super secret snippet".to_string()),
27046                snippet: None,
27047            },
27048            window,
27049            cx,
27050        );
27051
27052        // Language is specified in the action,
27053        // so the buffer language does not need to match
27054        editor.insert_snippet_at_selections(
27055            &InsertSnippet {
27056                language: Some("Markdown".to_string()),
27057                name: Some("location word".to_string()),
27058                snippet: None,
27059            },
27060            window,
27061            cx,
27062        );
27063
27064        editor.insert_snippet_at_selections(
27065            &InsertSnippet {
27066                language: None,
27067                name: None,
27068                snippet: Some("$0 after".to_string()),
27069            },
27070            window,
27071            cx,
27072        );
27073    });
27074
27075    cx.assert_editor_state(
27076        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
27077    );
27078}
27079
27080#[gpui::test(iterations = 10)]
27081async fn test_document_colors(cx: &mut TestAppContext) {
27082    let expected_color = Rgba {
27083        r: 0.33,
27084        g: 0.33,
27085        b: 0.33,
27086        a: 0.33,
27087    };
27088
27089    init_test(cx, |_| {});
27090
27091    let fs = FakeFs::new(cx.executor());
27092    fs.insert_tree(
27093        path!("/a"),
27094        json!({
27095            "first.rs": "fn main() { let a = 5; }",
27096        }),
27097    )
27098    .await;
27099
27100    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
27101    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27102    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27103
27104    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27105    language_registry.add(rust_lang());
27106    let mut fake_servers = language_registry.register_fake_lsp(
27107        "Rust",
27108        FakeLspAdapter {
27109            capabilities: lsp::ServerCapabilities {
27110                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27111                ..lsp::ServerCapabilities::default()
27112            },
27113            name: "rust-analyzer",
27114            ..FakeLspAdapter::default()
27115        },
27116    );
27117    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27118        "Rust",
27119        FakeLspAdapter {
27120            capabilities: lsp::ServerCapabilities {
27121                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27122                ..lsp::ServerCapabilities::default()
27123            },
27124            name: "not-rust-analyzer",
27125            ..FakeLspAdapter::default()
27126        },
27127    );
27128
27129    let editor = workspace
27130        .update(cx, |workspace, window, cx| {
27131            workspace.open_abs_path(
27132                PathBuf::from(path!("/a/first.rs")),
27133                OpenOptions::default(),
27134                window,
27135                cx,
27136            )
27137        })
27138        .unwrap()
27139        .await
27140        .unwrap()
27141        .downcast::<Editor>()
27142        .unwrap();
27143    let fake_language_server = fake_servers.next().await.unwrap();
27144    let fake_language_server_without_capabilities =
27145        fake_servers_without_capabilities.next().await.unwrap();
27146    let requests_made = Arc::new(AtomicUsize::new(0));
27147    let closure_requests_made = Arc::clone(&requests_made);
27148    let mut color_request_handle = fake_language_server
27149        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27150            let requests_made = Arc::clone(&closure_requests_made);
27151            async move {
27152                assert_eq!(
27153                    params.text_document.uri,
27154                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27155                );
27156                requests_made.fetch_add(1, atomic::Ordering::Release);
27157                Ok(vec![
27158                    lsp::ColorInformation {
27159                        range: lsp::Range {
27160                            start: lsp::Position {
27161                                line: 0,
27162                                character: 0,
27163                            },
27164                            end: lsp::Position {
27165                                line: 0,
27166                                character: 1,
27167                            },
27168                        },
27169                        color: lsp::Color {
27170                            red: 0.33,
27171                            green: 0.33,
27172                            blue: 0.33,
27173                            alpha: 0.33,
27174                        },
27175                    },
27176                    lsp::ColorInformation {
27177                        range: lsp::Range {
27178                            start: lsp::Position {
27179                                line: 0,
27180                                character: 0,
27181                            },
27182                            end: lsp::Position {
27183                                line: 0,
27184                                character: 1,
27185                            },
27186                        },
27187                        color: lsp::Color {
27188                            red: 0.33,
27189                            green: 0.33,
27190                            blue: 0.33,
27191                            alpha: 0.33,
27192                        },
27193                    },
27194                ])
27195            }
27196        });
27197
27198    let _handle = fake_language_server_without_capabilities
27199        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27200            panic!("Should not be called");
27201        });
27202    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27203    color_request_handle.next().await.unwrap();
27204    cx.run_until_parked();
27205    assert_eq!(
27206        1,
27207        requests_made.load(atomic::Ordering::Acquire),
27208        "Should query for colors once per editor open"
27209    );
27210    editor.update_in(cx, |editor, _, cx| {
27211        assert_eq!(
27212            vec![expected_color],
27213            extract_color_inlays(editor, cx),
27214            "Should have an initial inlay"
27215        );
27216    });
27217
27218    // opening another file in a split should not influence the LSP query counter
27219    workspace
27220        .update(cx, |workspace, window, cx| {
27221            assert_eq!(
27222                workspace.panes().len(),
27223                1,
27224                "Should have one pane with one editor"
27225            );
27226            workspace.move_item_to_pane_in_direction(
27227                &MoveItemToPaneInDirection {
27228                    direction: SplitDirection::Right,
27229                    focus: false,
27230                    clone: true,
27231                },
27232                window,
27233                cx,
27234            );
27235        })
27236        .unwrap();
27237    cx.run_until_parked();
27238    workspace
27239        .update(cx, |workspace, _, cx| {
27240            let panes = workspace.panes();
27241            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27242            for pane in panes {
27243                let editor = pane
27244                    .read(cx)
27245                    .active_item()
27246                    .and_then(|item| item.downcast::<Editor>())
27247                    .expect("Should have opened an editor in each split");
27248                let editor_file = editor
27249                    .read(cx)
27250                    .buffer()
27251                    .read(cx)
27252                    .as_singleton()
27253                    .expect("test deals with singleton buffers")
27254                    .read(cx)
27255                    .file()
27256                    .expect("test buffese should have a file")
27257                    .path();
27258                assert_eq!(
27259                    editor_file.as_ref(),
27260                    rel_path("first.rs"),
27261                    "Both editors should be opened for the same file"
27262                )
27263            }
27264        })
27265        .unwrap();
27266
27267    cx.executor().advance_clock(Duration::from_millis(500));
27268    let save = editor.update_in(cx, |editor, window, cx| {
27269        editor.move_to_end(&MoveToEnd, window, cx);
27270        editor.handle_input("dirty", window, cx);
27271        editor.save(
27272            SaveOptions {
27273                format: true,
27274                autosave: true,
27275            },
27276            project.clone(),
27277            window,
27278            cx,
27279        )
27280    });
27281    save.await.unwrap();
27282
27283    color_request_handle.next().await.unwrap();
27284    cx.run_until_parked();
27285    assert_eq!(
27286        2,
27287        requests_made.load(atomic::Ordering::Acquire),
27288        "Should query for colors once per save (deduplicated) and once per formatting after save"
27289    );
27290
27291    drop(editor);
27292    let close = workspace
27293        .update(cx, |workspace, window, cx| {
27294            workspace.active_pane().update(cx, |pane, cx| {
27295                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27296            })
27297        })
27298        .unwrap();
27299    close.await.unwrap();
27300    let close = workspace
27301        .update(cx, |workspace, window, cx| {
27302            workspace.active_pane().update(cx, |pane, cx| {
27303                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27304            })
27305        })
27306        .unwrap();
27307    close.await.unwrap();
27308    assert_eq!(
27309        2,
27310        requests_made.load(atomic::Ordering::Acquire),
27311        "After saving and closing all editors, no extra requests should be made"
27312    );
27313    workspace
27314        .update(cx, |workspace, _, cx| {
27315            assert!(
27316                workspace.active_item(cx).is_none(),
27317                "Should close all editors"
27318            )
27319        })
27320        .unwrap();
27321
27322    workspace
27323        .update(cx, |workspace, window, cx| {
27324            workspace.active_pane().update(cx, |pane, cx| {
27325                pane.navigate_backward(&workspace::GoBack, window, cx);
27326            })
27327        })
27328        .unwrap();
27329    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27330    cx.run_until_parked();
27331    let editor = workspace
27332        .update(cx, |workspace, _, cx| {
27333            workspace
27334                .active_item(cx)
27335                .expect("Should have reopened the editor again after navigating back")
27336                .downcast::<Editor>()
27337                .expect("Should be an editor")
27338        })
27339        .unwrap();
27340
27341    assert_eq!(
27342        2,
27343        requests_made.load(atomic::Ordering::Acquire),
27344        "Cache should be reused on buffer close and reopen"
27345    );
27346    editor.update(cx, |editor, cx| {
27347        assert_eq!(
27348            vec![expected_color],
27349            extract_color_inlays(editor, cx),
27350            "Should have an initial inlay"
27351        );
27352    });
27353
27354    drop(color_request_handle);
27355    let closure_requests_made = Arc::clone(&requests_made);
27356    let mut empty_color_request_handle = fake_language_server
27357        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27358            let requests_made = Arc::clone(&closure_requests_made);
27359            async move {
27360                assert_eq!(
27361                    params.text_document.uri,
27362                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27363                );
27364                requests_made.fetch_add(1, atomic::Ordering::Release);
27365                Ok(Vec::new())
27366            }
27367        });
27368    let save = editor.update_in(cx, |editor, window, cx| {
27369        editor.move_to_end(&MoveToEnd, window, cx);
27370        editor.handle_input("dirty_again", window, cx);
27371        editor.save(
27372            SaveOptions {
27373                format: false,
27374                autosave: true,
27375            },
27376            project.clone(),
27377            window,
27378            cx,
27379        )
27380    });
27381    save.await.unwrap();
27382
27383    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27384    empty_color_request_handle.next().await.unwrap();
27385    cx.run_until_parked();
27386    assert_eq!(
27387        3,
27388        requests_made.load(atomic::Ordering::Acquire),
27389        "Should query for colors once per save only, as formatting was not requested"
27390    );
27391    editor.update(cx, |editor, cx| {
27392        assert_eq!(
27393            Vec::<Rgba>::new(),
27394            extract_color_inlays(editor, cx),
27395            "Should clear all colors when the server returns an empty response"
27396        );
27397    });
27398}
27399
27400#[gpui::test]
27401async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27402    init_test(cx, |_| {});
27403    let (editor, cx) = cx.add_window_view(Editor::single_line);
27404    editor.update_in(cx, |editor, window, cx| {
27405        editor.set_text("oops\n\nwow\n", window, cx)
27406    });
27407    cx.run_until_parked();
27408    editor.update(cx, |editor, cx| {
27409        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27410    });
27411    editor.update(cx, |editor, cx| {
27412        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27413    });
27414    cx.run_until_parked();
27415    editor.update(cx, |editor, cx| {
27416        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27417    });
27418}
27419
27420#[gpui::test]
27421async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27422    init_test(cx, |_| {});
27423
27424    cx.update(|cx| {
27425        register_project_item::<Editor>(cx);
27426    });
27427
27428    let fs = FakeFs::new(cx.executor());
27429    fs.insert_tree("/root1", json!({})).await;
27430    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27431        .await;
27432
27433    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27434    let (workspace, cx) =
27435        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27436
27437    let worktree_id = project.update(cx, |project, cx| {
27438        project.worktrees(cx).next().unwrap().read(cx).id()
27439    });
27440
27441    let handle = workspace
27442        .update_in(cx, |workspace, window, cx| {
27443            let project_path = (worktree_id, rel_path("one.pdf"));
27444            workspace.open_path(project_path, None, true, window, cx)
27445        })
27446        .await
27447        .unwrap();
27448
27449    assert_eq!(
27450        handle.to_any_view().entity_type(),
27451        TypeId::of::<InvalidItemView>()
27452    );
27453}
27454
27455#[gpui::test]
27456async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27457    init_test(cx, |_| {});
27458
27459    let language = Arc::new(Language::new(
27460        LanguageConfig::default(),
27461        Some(tree_sitter_rust::LANGUAGE.into()),
27462    ));
27463
27464    // Test hierarchical sibling navigation
27465    let text = r#"
27466        fn outer() {
27467            if condition {
27468                let a = 1;
27469            }
27470            let b = 2;
27471        }
27472
27473        fn another() {
27474            let c = 3;
27475        }
27476    "#;
27477
27478    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27479    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27480    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27481
27482    // Wait for parsing to complete
27483    editor
27484        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27485        .await;
27486
27487    editor.update_in(cx, |editor, window, cx| {
27488        // Start by selecting "let a = 1;" inside the if block
27489        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27490            s.select_display_ranges([
27491                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27492            ]);
27493        });
27494
27495        let initial_selection = editor
27496            .selections
27497            .display_ranges(&editor.display_snapshot(cx));
27498        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27499
27500        // Test select next sibling - should move up levels to find the next sibling
27501        // Since "let a = 1;" has no siblings in the if block, it should move up
27502        // to find "let b = 2;" which is a sibling of the if block
27503        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27504        let next_selection = editor
27505            .selections
27506            .display_ranges(&editor.display_snapshot(cx));
27507
27508        // Should have a selection and it should be different from the initial
27509        assert_eq!(
27510            next_selection.len(),
27511            1,
27512            "Should have one selection after next"
27513        );
27514        assert_ne!(
27515            next_selection[0], initial_selection[0],
27516            "Next sibling selection should be different"
27517        );
27518
27519        // Test hierarchical navigation by going to the end of the current function
27520        // and trying to navigate to the next function
27521        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27522            s.select_display_ranges([
27523                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27524            ]);
27525        });
27526
27527        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27528        let function_next_selection = editor
27529            .selections
27530            .display_ranges(&editor.display_snapshot(cx));
27531
27532        // Should move to the next function
27533        assert_eq!(
27534            function_next_selection.len(),
27535            1,
27536            "Should have one selection after function next"
27537        );
27538
27539        // Test select previous sibling navigation
27540        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27541        let prev_selection = editor
27542            .selections
27543            .display_ranges(&editor.display_snapshot(cx));
27544
27545        // Should have a selection and it should be different
27546        assert_eq!(
27547            prev_selection.len(),
27548            1,
27549            "Should have one selection after prev"
27550        );
27551        assert_ne!(
27552            prev_selection[0], function_next_selection[0],
27553            "Previous sibling selection should be different from next"
27554        );
27555    });
27556}
27557
27558#[gpui::test]
27559async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27560    init_test(cx, |_| {});
27561
27562    let mut cx = EditorTestContext::new(cx).await;
27563    cx.set_state(
27564        "let ˇvariable = 42;
27565let another = variable + 1;
27566let result = variable * 2;",
27567    );
27568
27569    // Set up document highlights manually (simulating LSP response)
27570    cx.update_editor(|editor, _window, cx| {
27571        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27572
27573        // Create highlights for "variable" occurrences
27574        let highlight_ranges = [
27575            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27576            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27577            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27578        ];
27579
27580        let anchor_ranges: Vec<_> = highlight_ranges
27581            .iter()
27582            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27583            .collect();
27584
27585        editor.highlight_background::<DocumentHighlightRead>(
27586            &anchor_ranges,
27587            |_, theme| theme.colors().editor_document_highlight_read_background,
27588            cx,
27589        );
27590    });
27591
27592    // Go to next highlight - should move to second "variable"
27593    cx.update_editor(|editor, window, cx| {
27594        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27595    });
27596    cx.assert_editor_state(
27597        "let variable = 42;
27598let another = ˇvariable + 1;
27599let result = variable * 2;",
27600    );
27601
27602    // Go to next highlight - should move to third "variable"
27603    cx.update_editor(|editor, window, cx| {
27604        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27605    });
27606    cx.assert_editor_state(
27607        "let variable = 42;
27608let another = variable + 1;
27609let result = ˇvariable * 2;",
27610    );
27611
27612    // Go to next highlight - should stay at third "variable" (no wrap-around)
27613    cx.update_editor(|editor, window, cx| {
27614        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27615    });
27616    cx.assert_editor_state(
27617        "let variable = 42;
27618let another = variable + 1;
27619let result = ˇvariable * 2;",
27620    );
27621
27622    // Now test going backwards from third position
27623    cx.update_editor(|editor, window, cx| {
27624        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27625    });
27626    cx.assert_editor_state(
27627        "let variable = 42;
27628let another = ˇvariable + 1;
27629let result = variable * 2;",
27630    );
27631
27632    // Go to previous highlight - should move to first "variable"
27633    cx.update_editor(|editor, window, cx| {
27634        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27635    });
27636    cx.assert_editor_state(
27637        "let ˇvariable = 42;
27638let another = variable + 1;
27639let result = variable * 2;",
27640    );
27641
27642    // Go to previous highlight - should stay on first "variable"
27643    cx.update_editor(|editor, window, cx| {
27644        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27645    });
27646    cx.assert_editor_state(
27647        "let ˇvariable = 42;
27648let another = variable + 1;
27649let result = variable * 2;",
27650    );
27651}
27652
27653#[gpui::test]
27654async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27655    cx: &mut gpui::TestAppContext,
27656) {
27657    init_test(cx, |_| {});
27658
27659    let url = "https://zed.dev";
27660
27661    let markdown_language = Arc::new(Language::new(
27662        LanguageConfig {
27663            name: "Markdown".into(),
27664            ..LanguageConfig::default()
27665        },
27666        None,
27667    ));
27668
27669    let mut cx = EditorTestContext::new(cx).await;
27670    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27671    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27672
27673    cx.update_editor(|editor, window, cx| {
27674        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27675        editor.paste(&Paste, window, cx);
27676    });
27677
27678    cx.assert_editor_state(&format!(
27679        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27680    ));
27681}
27682
27683#[gpui::test]
27684async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
27685    init_test(cx, |_| {});
27686
27687    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
27688    let mut cx = EditorTestContext::new(cx).await;
27689
27690    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27691
27692    // Case 1: Test if adding a character with multi cursors preserves nested list indents
27693    cx.set_state(&indoc! {"
27694        - [ ] Item 1
27695            - [ ] Item 1.a
27696        - [ˇ] Item 2
27697            - [ˇ] Item 2.a
27698            - [ˇ] Item 2.b
27699        "
27700    });
27701    cx.update_editor(|editor, window, cx| {
27702        editor.handle_input("x", window, cx);
27703    });
27704    cx.run_until_parked();
27705    cx.assert_editor_state(indoc! {"
27706        - [ ] Item 1
27707            - [ ] Item 1.a
27708        - [xˇ] Item 2
27709            - [xˇ] Item 2.a
27710            - [xˇ] Item 2.b
27711        "
27712    });
27713
27714    // Case 2: Test adding new line after nested list preserves indent of previous line
27715    cx.set_state(&indoc! {"
27716        - [ ] Item 1
27717            - [ ] Item 1.a
27718        - [x] Item 2
27719            - [x] Item 2.a
27720            - [x] Item 2.bˇ"
27721    });
27722    cx.update_editor(|editor, window, cx| {
27723        editor.newline(&Newline, window, cx);
27724    });
27725    cx.assert_editor_state(indoc! {"
27726        - [ ] Item 1
27727            - [ ] Item 1.a
27728        - [x] Item 2
27729            - [x] Item 2.a
27730            - [x] Item 2.b
27731            ˇ"
27732    });
27733
27734    // Case 3: Test adding a new nested list item preserves indent
27735    cx.set_state(&indoc! {"
27736        - [ ] Item 1
27737            - [ ] Item 1.a
27738        - [x] Item 2
27739            - [x] Item 2.a
27740            - [x] Item 2.b
27741            ˇ"
27742    });
27743    cx.update_editor(|editor, window, cx| {
27744        editor.handle_input("-", window, cx);
27745    });
27746    cx.run_until_parked();
27747    cx.assert_editor_state(indoc! {"
27748        - [ ] Item 1
27749            - [ ] Item 1.a
27750        - [x] Item 2
27751            - [x] Item 2.a
27752            - [x] Item 2.b
27753"
27754    });
27755    cx.update_editor(|editor, window, cx| {
27756        editor.handle_input(" [x] Item 2.c", window, cx);
27757    });
27758    cx.run_until_parked();
27759    cx.assert_editor_state(indoc! {"
27760        - [ ] Item 1
27761            - [ ] Item 1.a
27762        - [x] Item 2
27763            - [x] Item 2.a
27764            - [x] Item 2.b
27765            - [x] Item 2.cˇ"
27766    });
27767
27768    // Case 4: Test adding new line after nested ordered list preserves indent of previous line
27769    cx.set_state(indoc! {"
27770        1. Item 1
27771            1. Item 1.a
27772        2. Item 2
27773            1. Item 2.a
27774            2. Item 2.bˇ"
27775    });
27776    cx.update_editor(|editor, window, cx| {
27777        editor.newline(&Newline, window, cx);
27778    });
27779    cx.assert_editor_state(indoc! {"
27780        1. Item 1
27781            1. Item 1.a
27782        2. Item 2
27783            1. Item 2.a
27784            2. Item 2.b
27785            ˇ"
27786    });
27787
27788    // Case 5: Adding new ordered list item preserves indent
27789    cx.set_state(indoc! {"
27790        1. Item 1
27791            1. Item 1.a
27792        2. Item 2
27793            1. Item 2.a
27794            2. Item 2.b
27795            ˇ"
27796    });
27797    cx.update_editor(|editor, window, cx| {
27798        editor.handle_input("3", window, cx);
27799    });
27800    cx.run_until_parked();
27801    cx.assert_editor_state(indoc! {"
27802        1. Item 1
27803            1. Item 1.a
27804        2. Item 2
27805            1. Item 2.a
27806            2. Item 2.b
27807"
27808    });
27809    cx.update_editor(|editor, window, cx| {
27810        editor.handle_input(".", window, cx);
27811    });
27812    cx.run_until_parked();
27813    cx.assert_editor_state(indoc! {"
27814        1. Item 1
27815            1. Item 1.a
27816        2. Item 2
27817            1. Item 2.a
27818            2. Item 2.b
27819            3.ˇ"
27820    });
27821    cx.update_editor(|editor, window, cx| {
27822        editor.handle_input(" Item 2.c", window, cx);
27823    });
27824    cx.run_until_parked();
27825    cx.assert_editor_state(indoc! {"
27826        1. Item 1
27827            1. Item 1.a
27828        2. Item 2
27829            1. Item 2.a
27830            2. Item 2.b
27831            3. Item 2.cˇ"
27832    });
27833
27834    // Case 6: Test adding new line after nested ordered list preserves indent of previous line
27835    cx.set_state(indoc! {"
27836        - Item 1
27837            - Item 1.a
27838            - Item 1.a
27839        ˇ"});
27840    cx.update_editor(|editor, window, cx| {
27841        editor.handle_input("-", window, cx);
27842    });
27843    cx.run_until_parked();
27844    cx.assert_editor_state(indoc! {"
27845        - Item 1
27846            - Item 1.a
27847            - Item 1.a
27848"});
27849
27850    // Case 7: Test blockquote newline preserves something
27851    cx.set_state(indoc! {"
27852        > Item 1ˇ"
27853    });
27854    cx.update_editor(|editor, window, cx| {
27855        editor.newline(&Newline, window, cx);
27856    });
27857    cx.assert_editor_state(indoc! {"
27858        > Item 1
27859        ˇ"
27860    });
27861}
27862
27863#[gpui::test]
27864async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27865    cx: &mut gpui::TestAppContext,
27866) {
27867    init_test(cx, |_| {});
27868
27869    let url = "https://zed.dev";
27870
27871    let markdown_language = Arc::new(Language::new(
27872        LanguageConfig {
27873            name: "Markdown".into(),
27874            ..LanguageConfig::default()
27875        },
27876        None,
27877    ));
27878
27879    let mut cx = EditorTestContext::new(cx).await;
27880    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27881    cx.set_state(&format!(
27882        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27883    ));
27884
27885    cx.update_editor(|editor, window, cx| {
27886        editor.copy(&Copy, window, cx);
27887    });
27888
27889    cx.set_state(&format!(
27890        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27891    ));
27892
27893    cx.update_editor(|editor, window, cx| {
27894        editor.paste(&Paste, window, cx);
27895    });
27896
27897    cx.assert_editor_state(&format!(
27898        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27899    ));
27900}
27901
27902#[gpui::test]
27903async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27904    cx: &mut gpui::TestAppContext,
27905) {
27906    init_test(cx, |_| {});
27907
27908    let url = "https://zed.dev";
27909
27910    let markdown_language = Arc::new(Language::new(
27911        LanguageConfig {
27912            name: "Markdown".into(),
27913            ..LanguageConfig::default()
27914        },
27915        None,
27916    ));
27917
27918    let mut cx = EditorTestContext::new(cx).await;
27919    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27920    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27921
27922    cx.update_editor(|editor, window, cx| {
27923        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27924        editor.paste(&Paste, window, cx);
27925    });
27926
27927    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27928}
27929
27930#[gpui::test]
27931async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27932    cx: &mut gpui::TestAppContext,
27933) {
27934    init_test(cx, |_| {});
27935
27936    let text = "Awesome";
27937
27938    let markdown_language = Arc::new(Language::new(
27939        LanguageConfig {
27940            name: "Markdown".into(),
27941            ..LanguageConfig::default()
27942        },
27943        None,
27944    ));
27945
27946    let mut cx = EditorTestContext::new(cx).await;
27947    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27948    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27949
27950    cx.update_editor(|editor, window, cx| {
27951        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27952        editor.paste(&Paste, window, cx);
27953    });
27954
27955    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27956}
27957
27958#[gpui::test]
27959async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27960    cx: &mut gpui::TestAppContext,
27961) {
27962    init_test(cx, |_| {});
27963
27964    let url = "https://zed.dev";
27965
27966    let markdown_language = Arc::new(Language::new(
27967        LanguageConfig {
27968            name: "Rust".into(),
27969            ..LanguageConfig::default()
27970        },
27971        None,
27972    ));
27973
27974    let mut cx = EditorTestContext::new(cx).await;
27975    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27976    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27977
27978    cx.update_editor(|editor, window, cx| {
27979        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27980        editor.paste(&Paste, window, cx);
27981    });
27982
27983    cx.assert_editor_state(&format!(
27984        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27985    ));
27986}
27987
27988#[gpui::test]
27989async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27990    cx: &mut TestAppContext,
27991) {
27992    init_test(cx, |_| {});
27993
27994    let url = "https://zed.dev";
27995
27996    let markdown_language = Arc::new(Language::new(
27997        LanguageConfig {
27998            name: "Markdown".into(),
27999            ..LanguageConfig::default()
28000        },
28001        None,
28002    ));
28003
28004    let (editor, cx) = cx.add_window_view(|window, cx| {
28005        let multi_buffer = MultiBuffer::build_multi(
28006            [
28007                ("this will embed -> link", vec![Point::row_range(0..1)]),
28008                ("this will replace -> link", vec![Point::row_range(0..1)]),
28009            ],
28010            cx,
28011        );
28012        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
28013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28014            s.select_ranges(vec![
28015                Point::new(0, 19)..Point::new(0, 23),
28016                Point::new(1, 21)..Point::new(1, 25),
28017            ])
28018        });
28019        let first_buffer_id = multi_buffer
28020            .read(cx)
28021            .excerpt_buffer_ids()
28022            .into_iter()
28023            .next()
28024            .unwrap();
28025        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
28026        first_buffer.update(cx, |buffer, cx| {
28027            buffer.set_language(Some(markdown_language.clone()), cx);
28028        });
28029
28030        editor
28031    });
28032    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28033
28034    cx.update_editor(|editor, window, cx| {
28035        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
28036        editor.paste(&Paste, window, cx);
28037    });
28038
28039    cx.assert_editor_state(&format!(
28040        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
28041    ));
28042}
28043
28044#[gpui::test]
28045async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
28046    init_test(cx, |_| {});
28047
28048    let fs = FakeFs::new(cx.executor());
28049    fs.insert_tree(
28050        path!("/project"),
28051        json!({
28052            "first.rs": "# First Document\nSome content here.",
28053            "second.rs": "Plain text content for second file.",
28054        }),
28055    )
28056    .await;
28057
28058    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
28059    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
28060    let cx = &mut VisualTestContext::from_window(*workspace, cx);
28061
28062    let language = rust_lang();
28063    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
28064    language_registry.add(language.clone());
28065    let mut fake_servers = language_registry.register_fake_lsp(
28066        "Rust",
28067        FakeLspAdapter {
28068            ..FakeLspAdapter::default()
28069        },
28070    );
28071
28072    let buffer1 = project
28073        .update(cx, |project, cx| {
28074            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
28075        })
28076        .await
28077        .unwrap();
28078    let buffer2 = project
28079        .update(cx, |project, cx| {
28080            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
28081        })
28082        .await
28083        .unwrap();
28084
28085    let multi_buffer = cx.new(|cx| {
28086        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
28087        multi_buffer.set_excerpts_for_path(
28088            PathKey::for_buffer(&buffer1, cx),
28089            buffer1.clone(),
28090            [Point::zero()..buffer1.read(cx).max_point()],
28091            3,
28092            cx,
28093        );
28094        multi_buffer.set_excerpts_for_path(
28095            PathKey::for_buffer(&buffer2, cx),
28096            buffer2.clone(),
28097            [Point::zero()..buffer1.read(cx).max_point()],
28098            3,
28099            cx,
28100        );
28101        multi_buffer
28102    });
28103
28104    let (editor, cx) = cx.add_window_view(|window, cx| {
28105        Editor::new(
28106            EditorMode::full(),
28107            multi_buffer,
28108            Some(project.clone()),
28109            window,
28110            cx,
28111        )
28112    });
28113
28114    let fake_language_server = fake_servers.next().await.unwrap();
28115
28116    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
28117
28118    let save = editor.update_in(cx, |editor, window, cx| {
28119        assert!(editor.is_dirty(cx));
28120
28121        editor.save(
28122            SaveOptions {
28123                format: true,
28124                autosave: true,
28125            },
28126            project,
28127            window,
28128            cx,
28129        )
28130    });
28131    let (start_edit_tx, start_edit_rx) = oneshot::channel();
28132    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28133    let mut done_edit_rx = Some(done_edit_rx);
28134    let mut start_edit_tx = Some(start_edit_tx);
28135
28136    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28137        start_edit_tx.take().unwrap().send(()).unwrap();
28138        let done_edit_rx = done_edit_rx.take().unwrap();
28139        async move {
28140            done_edit_rx.await.unwrap();
28141            Ok(None)
28142        }
28143    });
28144
28145    start_edit_rx.await.unwrap();
28146    buffer2
28147        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28148        .unwrap();
28149
28150    done_edit_tx.send(()).unwrap();
28151
28152    save.await.unwrap();
28153    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28154}
28155
28156#[track_caller]
28157fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28158    editor
28159        .all_inlays(cx)
28160        .into_iter()
28161        .filter_map(|inlay| inlay.get_color())
28162        .map(Rgba::from)
28163        .collect()
28164}
28165
28166#[gpui::test]
28167fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28168    init_test(cx, |_| {});
28169
28170    let editor = cx.add_window(|window, cx| {
28171        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28172        build_editor(buffer, window, cx)
28173    });
28174
28175    editor
28176        .update(cx, |editor, window, cx| {
28177            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28178                s.select_display_ranges([
28179                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28180                ])
28181            });
28182
28183            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28184
28185            assert_eq!(
28186                editor.display_text(cx),
28187                "line1\nline2\nline2",
28188                "Duplicating last line upward should create duplicate above, not on same line"
28189            );
28190
28191            assert_eq!(
28192                editor
28193                    .selections
28194                    .display_ranges(&editor.display_snapshot(cx)),
28195                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28196                "Selection should move to the duplicated line"
28197            );
28198        })
28199        .unwrap();
28200}
28201
28202#[gpui::test]
28203async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28204    init_test(cx, |_| {});
28205
28206    let mut cx = EditorTestContext::new(cx).await;
28207
28208    cx.set_state("line1\nline2ˇ");
28209
28210    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28211
28212    let clipboard_text = cx
28213        .read_from_clipboard()
28214        .and_then(|item| item.text().as_deref().map(str::to_string));
28215
28216    assert_eq!(
28217        clipboard_text,
28218        Some("line2\n".to_string()),
28219        "Copying a line without trailing newline should include a newline"
28220    );
28221
28222    cx.set_state("line1\nˇ");
28223
28224    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28225
28226    cx.assert_editor_state("line1\nline2\nˇ");
28227}
28228
28229#[gpui::test]
28230async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28231    init_test(cx, |_| {});
28232
28233    let mut cx = EditorTestContext::new(cx).await;
28234
28235    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28236
28237    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28238
28239    let clipboard_text = cx
28240        .read_from_clipboard()
28241        .and_then(|item| item.text().as_deref().map(str::to_string));
28242
28243    assert_eq!(
28244        clipboard_text,
28245        Some("line1\nline2\nline3\n".to_string()),
28246        "Copying multiple lines should include a single newline between lines"
28247    );
28248
28249    cx.set_state("lineA\nˇ");
28250
28251    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28252
28253    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28254}
28255
28256#[gpui::test]
28257async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28258    init_test(cx, |_| {});
28259
28260    let mut cx = EditorTestContext::new(cx).await;
28261
28262    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28263
28264    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28265
28266    let clipboard_text = cx
28267        .read_from_clipboard()
28268        .and_then(|item| item.text().as_deref().map(str::to_string));
28269
28270    assert_eq!(
28271        clipboard_text,
28272        Some("line1\nline2\nline3\n".to_string()),
28273        "Copying multiple lines should include a single newline between lines"
28274    );
28275
28276    cx.set_state("lineA\nˇ");
28277
28278    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28279
28280    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28281}
28282
28283#[gpui::test]
28284async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28285    init_test(cx, |_| {});
28286
28287    let mut cx = EditorTestContext::new(cx).await;
28288
28289    cx.set_state("line1\nline2ˇ");
28290    cx.update_editor(|e, window, cx| {
28291        e.set_mode(EditorMode::SingleLine);
28292        assert!(e.key_context(window, cx).contains("end_of_input"));
28293    });
28294    cx.set_state("ˇline1\nline2");
28295    cx.update_editor(|e, window, cx| {
28296        assert!(!e.key_context(window, cx).contains("end_of_input"));
28297    });
28298    cx.set_state("line1ˇ\nline2");
28299    cx.update_editor(|e, window, cx| {
28300        assert!(!e.key_context(window, cx).contains("end_of_input"));
28301    });
28302}
28303
28304#[gpui::test]
28305async fn test_sticky_scroll(cx: &mut TestAppContext) {
28306    init_test(cx, |_| {});
28307    let mut cx = EditorTestContext::new(cx).await;
28308
28309    let buffer = indoc! {"
28310            ˇfn foo() {
28311                let abc = 123;
28312            }
28313            struct Bar;
28314            impl Bar {
28315                fn new() -> Self {
28316                    Self
28317                }
28318            }
28319            fn baz() {
28320            }
28321        "};
28322    cx.set_state(&buffer);
28323
28324    cx.update_editor(|e, _, cx| {
28325        e.buffer()
28326            .read(cx)
28327            .as_singleton()
28328            .unwrap()
28329            .update(cx, |buffer, cx| {
28330                buffer.set_language(Some(rust_lang()), cx);
28331            })
28332    });
28333
28334    let mut sticky_headers = |offset: ScrollOffset| {
28335        cx.update_editor(|e, window, cx| {
28336            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28337            let style = e.style(cx).clone();
28338            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), &style, cx)
28339                .into_iter()
28340                .map(
28341                    |StickyHeader {
28342                         start_point,
28343                         offset,
28344                         ..
28345                     }| { (start_point, offset) },
28346                )
28347                .collect::<Vec<_>>()
28348        })
28349    };
28350
28351    let fn_foo = Point { row: 0, column: 0 };
28352    let impl_bar = Point { row: 4, column: 0 };
28353    let fn_new = Point { row: 5, column: 4 };
28354
28355    assert_eq!(sticky_headers(0.0), vec![]);
28356    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28357    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28358    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28359    assert_eq!(sticky_headers(2.0), vec![]);
28360    assert_eq!(sticky_headers(2.5), vec![]);
28361    assert_eq!(sticky_headers(3.0), vec![]);
28362    assert_eq!(sticky_headers(3.5), vec![]);
28363    assert_eq!(sticky_headers(4.0), vec![]);
28364    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28365    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28366    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28367    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28368    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28369    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28370    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28371    assert_eq!(sticky_headers(8.0), vec![]);
28372    assert_eq!(sticky_headers(8.5), vec![]);
28373    assert_eq!(sticky_headers(9.0), vec![]);
28374    assert_eq!(sticky_headers(9.5), vec![]);
28375    assert_eq!(sticky_headers(10.0), vec![]);
28376}
28377
28378#[gpui::test]
28379async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28380    init_test(cx, |_| {});
28381    cx.update(|cx| {
28382        SettingsStore::update_global(cx, |store, cx| {
28383            store.update_user_settings(cx, |settings| {
28384                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28385                    enabled: Some(true),
28386                })
28387            });
28388        });
28389    });
28390    let mut cx = EditorTestContext::new(cx).await;
28391
28392    let line_height = cx.update_editor(|editor, window, cx| {
28393        editor
28394            .style(cx)
28395            .text
28396            .line_height_in_pixels(window.rem_size())
28397    });
28398
28399    let buffer = indoc! {"
28400            ˇfn foo() {
28401                let abc = 123;
28402            }
28403            struct Bar;
28404            impl Bar {
28405                fn new() -> Self {
28406                    Self
28407                }
28408            }
28409            fn baz() {
28410            }
28411        "};
28412    cx.set_state(&buffer);
28413
28414    cx.update_editor(|e, _, cx| {
28415        e.buffer()
28416            .read(cx)
28417            .as_singleton()
28418            .unwrap()
28419            .update(cx, |buffer, cx| {
28420                buffer.set_language(Some(rust_lang()), cx);
28421            })
28422    });
28423
28424    let fn_foo = || empty_range(0, 0);
28425    let impl_bar = || empty_range(4, 0);
28426    let fn_new = || empty_range(5, 4);
28427
28428    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
28429        cx.update_editor(|e, window, cx| {
28430            e.scroll(
28431                gpui::Point {
28432                    x: 0.,
28433                    y: scroll_offset,
28434                },
28435                None,
28436                window,
28437                cx,
28438            );
28439        });
28440        cx.simulate_click(
28441            gpui::Point {
28442                x: px(0.),
28443                y: click_offset as f32 * line_height,
28444            },
28445            Modifiers::none(),
28446        );
28447        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
28448    };
28449
28450    assert_eq!(
28451        scroll_and_click(
28452            4.5, // impl Bar is halfway off the screen
28453            0.0  // click top of screen
28454        ),
28455        // scrolled to impl Bar
28456        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28457    );
28458
28459    assert_eq!(
28460        scroll_and_click(
28461            4.5,  // impl Bar is halfway off the screen
28462            0.25  // click middle of impl Bar
28463        ),
28464        // scrolled to impl Bar
28465        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28466    );
28467
28468    assert_eq!(
28469        scroll_and_click(
28470            4.5, // impl Bar is halfway off the screen
28471            1.5  // click below impl Bar (e.g. fn new())
28472        ),
28473        // scrolled to fn new() - this is below the impl Bar header which has persisted
28474        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28475    );
28476
28477    assert_eq!(
28478        scroll_and_click(
28479            5.5,  // fn new is halfway underneath impl Bar
28480            0.75  // click on the overlap of impl Bar and fn new()
28481        ),
28482        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28483    );
28484
28485    assert_eq!(
28486        scroll_and_click(
28487            5.5,  // fn new is halfway underneath impl Bar
28488            1.25  // click on the visible part of fn new()
28489        ),
28490        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28491    );
28492
28493    assert_eq!(
28494        scroll_and_click(
28495            1.5, // fn foo is halfway off the screen
28496            0.0  // click top of screen
28497        ),
28498        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28499    );
28500
28501    assert_eq!(
28502        scroll_and_click(
28503            1.5,  // fn foo is halfway off the screen
28504            0.75  // click visible part of let abc...
28505        )
28506        .0,
28507        // no change in scroll
28508        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28509        (gpui::Point { x: 0., y: 1.5 })
28510    );
28511}
28512
28513#[gpui::test]
28514async fn test_next_prev_reference(cx: &mut TestAppContext) {
28515    const CYCLE_POSITIONS: &[&'static str] = &[
28516        indoc! {"
28517            fn foo() {
28518                let ˇabc = 123;
28519                let x = abc + 1;
28520                let y = abc + 2;
28521                let z = abc + 2;
28522            }
28523        "},
28524        indoc! {"
28525            fn foo() {
28526                let abc = 123;
28527                let x = ˇabc + 1;
28528                let y = abc + 2;
28529                let z = abc + 2;
28530            }
28531        "},
28532        indoc! {"
28533            fn foo() {
28534                let abc = 123;
28535                let x = abc + 1;
28536                let y = ˇabc + 2;
28537                let z = abc + 2;
28538            }
28539        "},
28540        indoc! {"
28541            fn foo() {
28542                let abc = 123;
28543                let x = abc + 1;
28544                let y = abc + 2;
28545                let z = ˇabc + 2;
28546            }
28547        "},
28548    ];
28549
28550    init_test(cx, |_| {});
28551
28552    let mut cx = EditorLspTestContext::new_rust(
28553        lsp::ServerCapabilities {
28554            references_provider: Some(lsp::OneOf::Left(true)),
28555            ..Default::default()
28556        },
28557        cx,
28558    )
28559    .await;
28560
28561    // importantly, the cursor is in the middle
28562    cx.set_state(indoc! {"
28563        fn foo() {
28564            let aˇbc = 123;
28565            let x = abc + 1;
28566            let y = abc + 2;
28567            let z = abc + 2;
28568        }
28569    "});
28570
28571    let reference_ranges = [
28572        lsp::Position::new(1, 8),
28573        lsp::Position::new(2, 12),
28574        lsp::Position::new(3, 12),
28575        lsp::Position::new(4, 12),
28576    ]
28577    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28578
28579    cx.lsp
28580        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28581            Ok(Some(
28582                reference_ranges
28583                    .map(|range| lsp::Location {
28584                        uri: params.text_document_position.text_document.uri.clone(),
28585                        range,
28586                    })
28587                    .to_vec(),
28588            ))
28589        });
28590
28591    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28592        cx.update_editor(|editor, window, cx| {
28593            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28594        })
28595        .unwrap()
28596        .await
28597        .unwrap()
28598    };
28599
28600    _move(Direction::Next, 1, &mut cx).await;
28601    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28602
28603    _move(Direction::Next, 1, &mut cx).await;
28604    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28605
28606    _move(Direction::Next, 1, &mut cx).await;
28607    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28608
28609    // loops back to the start
28610    _move(Direction::Next, 1, &mut cx).await;
28611    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28612
28613    // loops back to the end
28614    _move(Direction::Prev, 1, &mut cx).await;
28615    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28616
28617    _move(Direction::Prev, 1, &mut cx).await;
28618    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28619
28620    _move(Direction::Prev, 1, &mut cx).await;
28621    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28622
28623    _move(Direction::Prev, 1, &mut cx).await;
28624    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28625
28626    _move(Direction::Next, 3, &mut cx).await;
28627    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28628
28629    _move(Direction::Prev, 2, &mut cx).await;
28630    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28631}
28632
28633#[gpui::test]
28634async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
28635    init_test(cx, |_| {});
28636
28637    let (editor, cx) = cx.add_window_view(|window, cx| {
28638        let multi_buffer = MultiBuffer::build_multi(
28639            [
28640                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28641                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28642            ],
28643            cx,
28644        );
28645        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28646    });
28647
28648    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28649    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28650
28651    cx.assert_excerpts_with_selections(indoc! {"
28652        [EXCERPT]
28653        ˇ1
28654        2
28655        3
28656        [EXCERPT]
28657        1
28658        2
28659        3
28660        "});
28661
28662    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28663    cx.update_editor(|editor, window, cx| {
28664        editor.change_selections(None.into(), window, cx, |s| {
28665            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28666        });
28667    });
28668    cx.assert_excerpts_with_selections(indoc! {"
28669        [EXCERPT]
28670        1
2867128672        3
28673        [EXCERPT]
28674        1
28675        2
28676        3
28677        "});
28678
28679    cx.update_editor(|editor, window, cx| {
28680        editor
28681            .select_all_matches(&SelectAllMatches, window, cx)
28682            .unwrap();
28683    });
28684    cx.assert_excerpts_with_selections(indoc! {"
28685        [EXCERPT]
28686        1
2868728688        3
28689        [EXCERPT]
28690        1
2869128692        3
28693        "});
28694
28695    cx.update_editor(|editor, window, cx| {
28696        editor.handle_input("X", window, cx);
28697    });
28698    cx.assert_excerpts_with_selections(indoc! {"
28699        [EXCERPT]
28700        1
2870128702        3
28703        [EXCERPT]
28704        1
2870528706        3
28707        "});
28708
28709    // Scenario 2: Select "2", then fold second buffer before insertion
28710    cx.update_multibuffer(|mb, cx| {
28711        for buffer_id in buffer_ids.iter() {
28712            let buffer = mb.buffer(*buffer_id).unwrap();
28713            buffer.update(cx, |buffer, cx| {
28714                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28715            });
28716        }
28717    });
28718
28719    // Select "2" and select all matches
28720    cx.update_editor(|editor, window, cx| {
28721        editor.change_selections(None.into(), window, cx, |s| {
28722            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28723        });
28724        editor
28725            .select_all_matches(&SelectAllMatches, window, cx)
28726            .unwrap();
28727    });
28728
28729    // Fold second buffer - should remove selections from folded buffer
28730    cx.update_editor(|editor, _, cx| {
28731        editor.fold_buffer(buffer_ids[1], cx);
28732    });
28733    cx.assert_excerpts_with_selections(indoc! {"
28734        [EXCERPT]
28735        1
2873628737        3
28738        [EXCERPT]
28739        [FOLDED]
28740        "});
28741
28742    // Insert text - should only affect first buffer
28743    cx.update_editor(|editor, window, cx| {
28744        editor.handle_input("Y", window, cx);
28745    });
28746    cx.update_editor(|editor, _, cx| {
28747        editor.unfold_buffer(buffer_ids[1], cx);
28748    });
28749    cx.assert_excerpts_with_selections(indoc! {"
28750        [EXCERPT]
28751        1
2875228753        3
28754        [EXCERPT]
28755        1
28756        2
28757        3
28758        "});
28759
28760    // Scenario 3: Select "2", then fold first buffer before insertion
28761    cx.update_multibuffer(|mb, cx| {
28762        for buffer_id in buffer_ids.iter() {
28763            let buffer = mb.buffer(*buffer_id).unwrap();
28764            buffer.update(cx, |buffer, cx| {
28765                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28766            });
28767        }
28768    });
28769
28770    // Select "2" and select all matches
28771    cx.update_editor(|editor, window, cx| {
28772        editor.change_selections(None.into(), window, cx, |s| {
28773            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28774        });
28775        editor
28776            .select_all_matches(&SelectAllMatches, window, cx)
28777            .unwrap();
28778    });
28779
28780    // Fold first buffer - should remove selections from folded buffer
28781    cx.update_editor(|editor, _, cx| {
28782        editor.fold_buffer(buffer_ids[0], cx);
28783    });
28784    cx.assert_excerpts_with_selections(indoc! {"
28785        [EXCERPT]
28786        [FOLDED]
28787        [EXCERPT]
28788        1
2878928790        3
28791        "});
28792
28793    // Insert text - should only affect second buffer
28794    cx.update_editor(|editor, window, cx| {
28795        editor.handle_input("Z", window, cx);
28796    });
28797    cx.update_editor(|editor, _, cx| {
28798        editor.unfold_buffer(buffer_ids[0], cx);
28799    });
28800    cx.assert_excerpts_with_selections(indoc! {"
28801        [EXCERPT]
28802        1
28803        2
28804        3
28805        [EXCERPT]
28806        1
2880728808        3
28809        "});
28810
28811    // Test correct folded header is selected upon fold
28812    cx.update_editor(|editor, _, cx| {
28813        editor.fold_buffer(buffer_ids[0], cx);
28814        editor.fold_buffer(buffer_ids[1], cx);
28815    });
28816    cx.assert_excerpts_with_selections(indoc! {"
28817        [EXCERPT]
28818        [FOLDED]
28819        [EXCERPT]
28820        ˇ[FOLDED]
28821        "});
28822
28823    // Test selection inside folded buffer unfolds it on type
28824    cx.update_editor(|editor, window, cx| {
28825        editor.handle_input("W", window, cx);
28826    });
28827    cx.update_editor(|editor, _, cx| {
28828        editor.unfold_buffer(buffer_ids[0], cx);
28829    });
28830    cx.assert_excerpts_with_selections(indoc! {"
28831        [EXCERPT]
28832        1
28833        2
28834        3
28835        [EXCERPT]
28836        Wˇ1
28837        Z
28838        3
28839        "});
28840}
28841
28842#[gpui::test]
28843async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
28844    init_test(cx, |_| {});
28845    let mut leader_cx = EditorTestContext::new(cx).await;
28846
28847    let diff_base = indoc!(
28848        r#"
28849        one
28850        two
28851        three
28852        four
28853        five
28854        six
28855        "#
28856    );
28857
28858    let initial_state = indoc!(
28859        r#"
28860        ˇone
28861        two
28862        THREE
28863        four
28864        five
28865        six
28866        "#
28867    );
28868
28869    leader_cx.set_state(initial_state);
28870
28871    leader_cx.set_head_text(&diff_base);
28872    leader_cx.run_until_parked();
28873
28874    let follower = leader_cx.update_multibuffer(|leader, cx| {
28875        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28876        leader.set_all_diff_hunks_expanded(cx);
28877        leader.get_or_create_follower(cx)
28878    });
28879    follower.update(cx, |follower, cx| {
28880        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28881        follower.set_all_diff_hunks_expanded(cx);
28882    });
28883
28884    let follower_editor =
28885        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
28886    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
28887
28888    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
28889    cx.run_until_parked();
28890
28891    leader_cx.assert_editor_state(initial_state);
28892    follower_cx.assert_editor_state(indoc! {
28893        r#"
28894        ˇone
28895        two
28896        three
28897        four
28898        five
28899        six
28900        "#
28901    });
28902
28903    follower_cx.editor(|editor, _window, cx| {
28904        assert!(editor.read_only(cx));
28905    });
28906
28907    leader_cx.update_editor(|editor, _window, cx| {
28908        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
28909    });
28910    cx.run_until_parked();
28911
28912    leader_cx.assert_editor_state(indoc! {
28913        r#"
28914        ˇone
28915        two
28916        THREE
28917        four
28918        FIVE
28919        six
28920        "#
28921    });
28922
28923    follower_cx.assert_editor_state(indoc! {
28924        r#"
28925        ˇone
28926        two
28927        three
28928        four
28929        five
28930        six
28931        "#
28932    });
28933
28934    leader_cx.update_editor(|editor, _window, cx| {
28935        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
28936    });
28937    cx.run_until_parked();
28938
28939    leader_cx.assert_editor_state(indoc! {
28940        r#"
28941        ˇone
28942        two
28943        THREE
28944        four
28945        FIVE
28946        six
28947        SEVEN"#
28948    });
28949
28950    follower_cx.assert_editor_state(indoc! {
28951        r#"
28952        ˇone
28953        two
28954        three
28955        four
28956        five
28957        six
28958        "#
28959    });
28960
28961    leader_cx.update_editor(|editor, window, cx| {
28962        editor.move_down(&MoveDown, window, cx);
28963        editor.refresh_selected_text_highlights(true, window, cx);
28964    });
28965    leader_cx.run_until_parked();
28966}
28967
28968#[gpui::test]
28969async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
28970    init_test(cx, |_| {});
28971    let base_text = "base\n";
28972    let buffer_text = "buffer\n";
28973
28974    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
28975    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
28976
28977    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
28978    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
28979    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
28980    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
28981
28982    let leader = cx.new(|cx| {
28983        let mut leader = MultiBuffer::new(Capability::ReadWrite);
28984        leader.set_all_diff_hunks_expanded(cx);
28985        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28986        leader
28987    });
28988    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
28989    follower.update(cx, |follower, _| {
28990        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28991    });
28992
28993    leader.update(cx, |leader, cx| {
28994        leader.insert_excerpts_after(
28995            ExcerptId::min(),
28996            extra_buffer_2.clone(),
28997            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28998            cx,
28999        );
29000        leader.add_diff(extra_diff_2.clone(), cx);
29001
29002        leader.insert_excerpts_after(
29003            ExcerptId::min(),
29004            extra_buffer_1.clone(),
29005            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29006            cx,
29007        );
29008        leader.add_diff(extra_diff_1.clone(), cx);
29009
29010        leader.insert_excerpts_after(
29011            ExcerptId::min(),
29012            buffer1.clone(),
29013            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
29014            cx,
29015        );
29016        leader.add_diff(diff1.clone(), cx);
29017    });
29018
29019    cx.run_until_parked();
29020    let mut cx = cx.add_empty_window();
29021
29022    let leader_editor = cx
29023        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
29024    let follower_editor = cx.new_window_entity(|window, cx| {
29025        Editor::for_multibuffer(follower.clone(), None, window, cx)
29026    });
29027
29028    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
29029    leader_cx.assert_editor_state(indoc! {"
29030       ˇbuffer
29031
29032       dummy text 1
29033
29034       dummy text 2
29035    "});
29036    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
29037    follower_cx.assert_editor_state(indoc! {"
29038        ˇbase
29039
29040
29041    "});
29042}
29043
29044#[gpui::test]
29045async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
29046    init_test(cx, |_| {});
29047
29048    let (editor, cx) = cx.add_window_view(|window, cx| {
29049        let multi_buffer = MultiBuffer::build_multi(
29050            [
29051                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
29052                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
29053            ],
29054            cx,
29055        );
29056        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
29057    });
29058
29059    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
29060
29061    cx.assert_excerpts_with_selections(indoc! {"
29062        [EXCERPT]
29063        ˇ1
29064        2
29065        3
29066        [EXCERPT]
29067        1
29068        2
29069        3
29070        4
29071        5
29072        6
29073        7
29074        8
29075        9
29076        "});
29077
29078    cx.update_editor(|editor, window, cx| {
29079        editor.change_selections(None.into(), window, cx, |s| {
29080            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
29081        });
29082    });
29083
29084    cx.assert_excerpts_with_selections(indoc! {"
29085        [EXCERPT]
29086        1
29087        2
29088        3
29089        [EXCERPT]
29090        1
29091        2
29092        3
29093        4
29094        5
29095        6
29096        ˇ7
29097        8
29098        9
29099        "});
29100
29101    cx.update_editor(|editor, _window, cx| {
29102        editor.set_vertical_scroll_margin(0, cx);
29103    });
29104
29105    cx.update_editor(|editor, window, cx| {
29106        assert_eq!(editor.vertical_scroll_margin(), 0);
29107        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29108        assert_eq!(
29109            editor.snapshot(window, cx).scroll_position(),
29110            gpui::Point::new(0., 12.0)
29111        );
29112    });
29113
29114    cx.update_editor(|editor, _window, cx| {
29115        editor.set_vertical_scroll_margin(3, cx);
29116    });
29117
29118    cx.update_editor(|editor, window, cx| {
29119        assert_eq!(editor.vertical_scroll_margin(), 3);
29120        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
29121        assert_eq!(
29122            editor.snapshot(window, cx).scroll_position(),
29123            gpui::Point::new(0., 9.0)
29124        );
29125    });
29126}
29127
29128#[gpui::test]
29129async fn test_find_references_single_case(cx: &mut TestAppContext) {
29130    init_test(cx, |_| {});
29131    let mut cx = EditorLspTestContext::new_rust(
29132        lsp::ServerCapabilities {
29133            references_provider: Some(lsp::OneOf::Left(true)),
29134            ..lsp::ServerCapabilities::default()
29135        },
29136        cx,
29137    )
29138    .await;
29139
29140    let before = indoc!(
29141        r#"
29142        fn main() {
29143            let aˇbc = 123;
29144            let xyz = abc;
29145        }
29146        "#
29147    );
29148    let after = indoc!(
29149        r#"
29150        fn main() {
29151            let abc = 123;
29152            let xyz = ˇabc;
29153        }
29154        "#
29155    );
29156
29157    cx.lsp
29158        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29159            Ok(Some(vec![
29160                lsp::Location {
29161                    uri: params.text_document_position.text_document.uri.clone(),
29162                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29163                },
29164                lsp::Location {
29165                    uri: params.text_document_position.text_document.uri,
29166                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29167                },
29168            ]))
29169        });
29170
29171    cx.set_state(before);
29172
29173    let action = FindAllReferences {
29174        always_open_multibuffer: false,
29175    };
29176
29177    let navigated = cx
29178        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29179        .expect("should have spawned a task")
29180        .await
29181        .unwrap();
29182
29183    assert_eq!(navigated, Navigated::No);
29184
29185    cx.run_until_parked();
29186
29187    cx.assert_editor_state(after);
29188}