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.editor(|editor, window, _| {
 2222        editor
 2223            .style()
 2224            .unwrap()
 2225            .text
 2226            .line_height_in_pixels(window.rem_size())
 2227    });
 2228    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2229
 2230    cx.set_state(
 2231        &r#"ˇone
 2232        two
 2233
 2234        three
 2235        fourˇ
 2236        five
 2237
 2238        six"#
 2239            .unindent(),
 2240    );
 2241
 2242    cx.update_editor(|editor, window, cx| {
 2243        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2244    });
 2245    cx.assert_editor_state(
 2246        &r#"one
 2247        two
 2248        ˇ
 2249        three
 2250        four
 2251        five
 2252        ˇ
 2253        six"#
 2254            .unindent(),
 2255    );
 2256
 2257    cx.update_editor(|editor, window, cx| {
 2258        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2259    });
 2260    cx.assert_editor_state(
 2261        &r#"one
 2262        two
 2263
 2264        three
 2265        four
 2266        five
 2267        ˇ
 2268        sixˇ"#
 2269            .unindent(),
 2270    );
 2271
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2274    });
 2275    cx.assert_editor_state(
 2276        &r#"one
 2277        two
 2278
 2279        three
 2280        four
 2281        five
 2282
 2283        sixˇ"#
 2284            .unindent(),
 2285    );
 2286
 2287    cx.update_editor(|editor, window, cx| {
 2288        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2289    });
 2290    cx.assert_editor_state(
 2291        &r#"one
 2292        two
 2293
 2294        three
 2295        four
 2296        five
 2297        ˇ
 2298        six"#
 2299            .unindent(),
 2300    );
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(
 2306        &r#"one
 2307        two
 2308        ˇ
 2309        three
 2310        four
 2311        five
 2312
 2313        six"#
 2314            .unindent(),
 2315    );
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(
 2321        &r#"ˇone
 2322        two
 2323
 2324        three
 2325        four
 2326        five
 2327
 2328        six"#
 2329            .unindent(),
 2330    );
 2331}
 2332
 2333#[gpui::test]
 2334async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2335    init_test(cx, |_| {});
 2336    let mut cx = EditorTestContext::new(cx).await;
 2337    let line_height = cx.editor(|editor, window, _| {
 2338        editor
 2339            .style()
 2340            .unwrap()
 2341            .text
 2342            .line_height_in_pixels(window.rem_size())
 2343    });
 2344    let window = cx.window;
 2345    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2346
 2347    cx.set_state(
 2348        r#"ˇone
 2349        two
 2350        three
 2351        four
 2352        five
 2353        six
 2354        seven
 2355        eight
 2356        nine
 2357        ten
 2358        "#,
 2359    );
 2360
 2361    cx.update_editor(|editor, window, cx| {
 2362        assert_eq!(
 2363            editor.snapshot(window, cx).scroll_position(),
 2364            gpui::Point::new(0., 0.)
 2365        );
 2366        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2367        assert_eq!(
 2368            editor.snapshot(window, cx).scroll_position(),
 2369            gpui::Point::new(0., 3.)
 2370        );
 2371        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2372        assert_eq!(
 2373            editor.snapshot(window, cx).scroll_position(),
 2374            gpui::Point::new(0., 6.)
 2375        );
 2376        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2377        assert_eq!(
 2378            editor.snapshot(window, cx).scroll_position(),
 2379            gpui::Point::new(0., 3.)
 2380        );
 2381
 2382        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2383        assert_eq!(
 2384            editor.snapshot(window, cx).scroll_position(),
 2385            gpui::Point::new(0., 1.)
 2386        );
 2387        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2388        assert_eq!(
 2389            editor.snapshot(window, cx).scroll_position(),
 2390            gpui::Point::new(0., 3.)
 2391        );
 2392    });
 2393}
 2394
 2395#[gpui::test]
 2396async fn test_autoscroll(cx: &mut TestAppContext) {
 2397    init_test(cx, |_| {});
 2398    let mut cx = EditorTestContext::new(cx).await;
 2399
 2400    let line_height = cx.update_editor(|editor, window, cx| {
 2401        editor.set_vertical_scroll_margin(2, cx);
 2402        editor
 2403            .style()
 2404            .unwrap()
 2405            .text
 2406            .line_height_in_pixels(window.rem_size())
 2407    });
 2408    let window = cx.window;
 2409    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2410
 2411    cx.set_state(
 2412        r#"ˇone
 2413            two
 2414            three
 2415            four
 2416            five
 2417            six
 2418            seven
 2419            eight
 2420            nine
 2421            ten
 2422        "#,
 2423    );
 2424    cx.update_editor(|editor, window, cx| {
 2425        assert_eq!(
 2426            editor.snapshot(window, cx).scroll_position(),
 2427            gpui::Point::new(0., 0.0)
 2428        );
 2429    });
 2430
 2431    // Add a cursor below the visible area. Since both cursors cannot fit
 2432    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2433    // allows the vertical scroll margin below that cursor.
 2434    cx.update_editor(|editor, window, cx| {
 2435        editor.change_selections(Default::default(), window, cx, |selections| {
 2436            selections.select_ranges([
 2437                Point::new(0, 0)..Point::new(0, 0),
 2438                Point::new(6, 0)..Point::new(6, 0),
 2439            ]);
 2440        })
 2441    });
 2442    cx.update_editor(|editor, window, cx| {
 2443        assert_eq!(
 2444            editor.snapshot(window, cx).scroll_position(),
 2445            gpui::Point::new(0., 3.0)
 2446        );
 2447    });
 2448
 2449    // Move down. The editor cursor scrolls down to track the newest cursor.
 2450    cx.update_editor(|editor, window, cx| {
 2451        editor.move_down(&Default::default(), window, cx);
 2452    });
 2453    cx.update_editor(|editor, window, cx| {
 2454        assert_eq!(
 2455            editor.snapshot(window, cx).scroll_position(),
 2456            gpui::Point::new(0., 4.0)
 2457        );
 2458    });
 2459
 2460    // Add a cursor above the visible area. Since both cursors fit on screen,
 2461    // the editor scrolls to show both.
 2462    cx.update_editor(|editor, window, cx| {
 2463        editor.change_selections(Default::default(), window, cx, |selections| {
 2464            selections.select_ranges([
 2465                Point::new(1, 0)..Point::new(1, 0),
 2466                Point::new(6, 0)..Point::new(6, 0),
 2467            ]);
 2468        })
 2469    });
 2470    cx.update_editor(|editor, window, cx| {
 2471        assert_eq!(
 2472            editor.snapshot(window, cx).scroll_position(),
 2473            gpui::Point::new(0., 1.0)
 2474        );
 2475    });
 2476}
 2477
 2478#[gpui::test]
 2479async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481    let mut cx = EditorTestContext::new(cx).await;
 2482
 2483    let line_height = cx.editor(|editor, window, _cx| {
 2484        editor
 2485            .style()
 2486            .unwrap()
 2487            .text
 2488            .line_height_in_pixels(window.rem_size())
 2489    });
 2490    let window = cx.window;
 2491    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2492    cx.set_state(
 2493        &r#"
 2494        ˇone
 2495        two
 2496        threeˇ
 2497        four
 2498        five
 2499        six
 2500        seven
 2501        eight
 2502        nine
 2503        ten
 2504        "#
 2505        .unindent(),
 2506    );
 2507
 2508    cx.update_editor(|editor, window, cx| {
 2509        editor.move_page_down(&MovePageDown::default(), window, cx)
 2510    });
 2511    cx.assert_editor_state(
 2512        &r#"
 2513        one
 2514        two
 2515        three
 2516        ˇfour
 2517        five
 2518        sixˇ
 2519        seven
 2520        eight
 2521        nine
 2522        ten
 2523        "#
 2524        .unindent(),
 2525    );
 2526
 2527    cx.update_editor(|editor, window, cx| {
 2528        editor.move_page_down(&MovePageDown::default(), window, cx)
 2529    });
 2530    cx.assert_editor_state(
 2531        &r#"
 2532        one
 2533        two
 2534        three
 2535        four
 2536        five
 2537        six
 2538        ˇseven
 2539        eight
 2540        nineˇ
 2541        ten
 2542        "#
 2543        .unindent(),
 2544    );
 2545
 2546    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2547    cx.assert_editor_state(
 2548        &r#"
 2549        one
 2550        two
 2551        three
 2552        ˇfour
 2553        five
 2554        sixˇ
 2555        seven
 2556        eight
 2557        nine
 2558        ten
 2559        "#
 2560        .unindent(),
 2561    );
 2562
 2563    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2564    cx.assert_editor_state(
 2565        &r#"
 2566        ˇone
 2567        two
 2568        threeˇ
 2569        four
 2570        five
 2571        six
 2572        seven
 2573        eight
 2574        nine
 2575        ten
 2576        "#
 2577        .unindent(),
 2578    );
 2579
 2580    // Test select collapsing
 2581    cx.update_editor(|editor, window, cx| {
 2582        editor.move_page_down(&MovePageDown::default(), window, cx);
 2583        editor.move_page_down(&MovePageDown::default(), window, cx);
 2584        editor.move_page_down(&MovePageDown::default(), window, cx);
 2585    });
 2586    cx.assert_editor_state(
 2587        &r#"
 2588        one
 2589        two
 2590        three
 2591        four
 2592        five
 2593        six
 2594        seven
 2595        eight
 2596        nine
 2597        ˇten
 2598        ˇ"#
 2599        .unindent(),
 2600    );
 2601}
 2602
 2603#[gpui::test]
 2604async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2605    init_test(cx, |_| {});
 2606    let mut cx = EditorTestContext::new(cx).await;
 2607    cx.set_state("one «two threeˇ» four");
 2608    cx.update_editor(|editor, window, cx| {
 2609        editor.delete_to_beginning_of_line(
 2610            &DeleteToBeginningOfLine {
 2611                stop_at_indent: false,
 2612            },
 2613            window,
 2614            cx,
 2615        );
 2616        assert_eq!(editor.text(cx), " four");
 2617    });
 2618}
 2619
 2620#[gpui::test]
 2621async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2622    init_test(cx, |_| {});
 2623
 2624    let mut cx = EditorTestContext::new(cx).await;
 2625
 2626    // For an empty selection, the preceding word fragment is deleted.
 2627    // For non-empty selections, only selected characters are deleted.
 2628    cx.set_state("onˇe two t«hreˇ»e four");
 2629    cx.update_editor(|editor, window, cx| {
 2630        editor.delete_to_previous_word_start(
 2631            &DeleteToPreviousWordStart {
 2632                ignore_newlines: false,
 2633                ignore_brackets: false,
 2634            },
 2635            window,
 2636            cx,
 2637        );
 2638    });
 2639    cx.assert_editor_state("ˇe two tˇe four");
 2640
 2641    cx.set_state("e tˇwo te «fˇ»our");
 2642    cx.update_editor(|editor, window, cx| {
 2643        editor.delete_to_next_word_end(
 2644            &DeleteToNextWordEnd {
 2645                ignore_newlines: false,
 2646                ignore_brackets: false,
 2647            },
 2648            window,
 2649            cx,
 2650        );
 2651    });
 2652    cx.assert_editor_state("e tˇ te ˇour");
 2653}
 2654
 2655#[gpui::test]
 2656async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2657    init_test(cx, |_| {});
 2658
 2659    let mut cx = EditorTestContext::new(cx).await;
 2660
 2661    cx.set_state("here is some text    ˇwith a space");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_previous_word_start(
 2664            &DeleteToPreviousWordStart {
 2665                ignore_newlines: false,
 2666                ignore_brackets: true,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2673    cx.assert_editor_state("here is some textˇwith a space");
 2674
 2675    cx.set_state("here is some text    ˇwith a space");
 2676    cx.update_editor(|editor, window, cx| {
 2677        editor.delete_to_previous_word_start(
 2678            &DeleteToPreviousWordStart {
 2679                ignore_newlines: false,
 2680                ignore_brackets: false,
 2681            },
 2682            window,
 2683            cx,
 2684        );
 2685    });
 2686    cx.assert_editor_state("here is some textˇwith a space");
 2687
 2688    cx.set_state("here is some textˇ    with a space");
 2689    cx.update_editor(|editor, window, cx| {
 2690        editor.delete_to_next_word_end(
 2691            &DeleteToNextWordEnd {
 2692                ignore_newlines: false,
 2693                ignore_brackets: true,
 2694            },
 2695            window,
 2696            cx,
 2697        );
 2698    });
 2699    // Same happens in the other direction.
 2700    cx.assert_editor_state("here is some textˇwith a space");
 2701
 2702    cx.set_state("here is some textˇ    with a space");
 2703    cx.update_editor(|editor, window, cx| {
 2704        editor.delete_to_next_word_end(
 2705            &DeleteToNextWordEnd {
 2706                ignore_newlines: false,
 2707                ignore_brackets: false,
 2708            },
 2709            window,
 2710            cx,
 2711        );
 2712    });
 2713    cx.assert_editor_state("here is some textˇwith a space");
 2714
 2715    cx.set_state("here is some textˇ    with a space");
 2716    cx.update_editor(|editor, window, cx| {
 2717        editor.delete_to_next_word_end(
 2718            &DeleteToNextWordEnd {
 2719                ignore_newlines: true,
 2720                ignore_brackets: false,
 2721            },
 2722            window,
 2723            cx,
 2724        );
 2725    });
 2726    cx.assert_editor_state("here is some textˇwith a space");
 2727    cx.update_editor(|editor, window, cx| {
 2728        editor.delete_to_previous_word_start(
 2729            &DeleteToPreviousWordStart {
 2730                ignore_newlines: true,
 2731                ignore_brackets: false,
 2732            },
 2733            window,
 2734            cx,
 2735        );
 2736    });
 2737    cx.assert_editor_state("here is some ˇwith a space");
 2738    cx.update_editor(|editor, window, cx| {
 2739        editor.delete_to_previous_word_start(
 2740            &DeleteToPreviousWordStart {
 2741                ignore_newlines: true,
 2742                ignore_brackets: false,
 2743            },
 2744            window,
 2745            cx,
 2746        );
 2747    });
 2748    // Single whitespaces are removed with the word behind them.
 2749    cx.assert_editor_state("here is ˇwith a space");
 2750    cx.update_editor(|editor, window, cx| {
 2751        editor.delete_to_previous_word_start(
 2752            &DeleteToPreviousWordStart {
 2753                ignore_newlines: true,
 2754                ignore_brackets: false,
 2755            },
 2756            window,
 2757            cx,
 2758        );
 2759    });
 2760    cx.assert_editor_state("here ˇwith a space");
 2761    cx.update_editor(|editor, window, cx| {
 2762        editor.delete_to_previous_word_start(
 2763            &DeleteToPreviousWordStart {
 2764                ignore_newlines: true,
 2765                ignore_brackets: false,
 2766            },
 2767            window,
 2768            cx,
 2769        );
 2770    });
 2771    cx.assert_editor_state("ˇwith a space");
 2772    cx.update_editor(|editor, window, cx| {
 2773        editor.delete_to_previous_word_start(
 2774            &DeleteToPreviousWordStart {
 2775                ignore_newlines: true,
 2776                ignore_brackets: false,
 2777            },
 2778            window,
 2779            cx,
 2780        );
 2781    });
 2782    cx.assert_editor_state("ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_next_word_end(
 2785            &DeleteToNextWordEnd {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    // Same happens in the other direction.
 2794    cx.assert_editor_state("ˇ a space");
 2795    cx.update_editor(|editor, window, cx| {
 2796        editor.delete_to_next_word_end(
 2797            &DeleteToNextWordEnd {
 2798                ignore_newlines: true,
 2799                ignore_brackets: false,
 2800            },
 2801            window,
 2802            cx,
 2803        );
 2804    });
 2805    cx.assert_editor_state("ˇ space");
 2806    cx.update_editor(|editor, window, cx| {
 2807        editor.delete_to_next_word_end(
 2808            &DeleteToNextWordEnd {
 2809                ignore_newlines: true,
 2810                ignore_brackets: false,
 2811            },
 2812            window,
 2813            cx,
 2814        );
 2815    });
 2816    cx.assert_editor_state("ˇ");
 2817    cx.update_editor(|editor, window, cx| {
 2818        editor.delete_to_next_word_end(
 2819            &DeleteToNextWordEnd {
 2820                ignore_newlines: true,
 2821                ignore_brackets: false,
 2822            },
 2823            window,
 2824            cx,
 2825        );
 2826    });
 2827    cx.assert_editor_state("ˇ");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_previous_word_start(
 2830            &DeleteToPreviousWordStart {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ");
 2839}
 2840
 2841#[gpui::test]
 2842async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2843    init_test(cx, |_| {});
 2844
 2845    let language = Arc::new(
 2846        Language::new(
 2847            LanguageConfig {
 2848                brackets: BracketPairConfig {
 2849                    pairs: vec![
 2850                        BracketPair {
 2851                            start: "\"".to_string(),
 2852                            end: "\"".to_string(),
 2853                            close: true,
 2854                            surround: true,
 2855                            newline: false,
 2856                        },
 2857                        BracketPair {
 2858                            start: "(".to_string(),
 2859                            end: ")".to_string(),
 2860                            close: true,
 2861                            surround: true,
 2862                            newline: true,
 2863                        },
 2864                    ],
 2865                    ..BracketPairConfig::default()
 2866                },
 2867                ..LanguageConfig::default()
 2868            },
 2869            Some(tree_sitter_rust::LANGUAGE.into()),
 2870        )
 2871        .with_brackets_query(
 2872            r#"
 2873                ("(" @open ")" @close)
 2874                ("\"" @open "\"" @close)
 2875            "#,
 2876        )
 2877        .unwrap(),
 2878    );
 2879
 2880    let mut cx = EditorTestContext::new(cx).await;
 2881    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 2882
 2883    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2884    cx.update_editor(|editor, window, cx| {
 2885        editor.delete_to_previous_word_start(
 2886            &DeleteToPreviousWordStart {
 2887                ignore_newlines: true,
 2888                ignore_brackets: false,
 2889            },
 2890            window,
 2891            cx,
 2892        );
 2893    });
 2894    // Deletion stops before brackets if asked to not ignore them.
 2895    cx.assert_editor_state(r#"macro!("ˇCOMMENT");"#);
 2896    cx.update_editor(|editor, window, cx| {
 2897        editor.delete_to_previous_word_start(
 2898            &DeleteToPreviousWordStart {
 2899                ignore_newlines: true,
 2900                ignore_brackets: false,
 2901            },
 2902            window,
 2903            cx,
 2904        );
 2905    });
 2906    // Deletion has to remove a single bracket and then stop again.
 2907    cx.assert_editor_state(r#"macro!(ˇCOMMENT");"#);
 2908
 2909    cx.update_editor(|editor, window, cx| {
 2910        editor.delete_to_previous_word_start(
 2911            &DeleteToPreviousWordStart {
 2912                ignore_newlines: true,
 2913                ignore_brackets: false,
 2914            },
 2915            window,
 2916            cx,
 2917        );
 2918    });
 2919    cx.assert_editor_state(r#"macro!ˇCOMMENT");"#);
 2920
 2921    cx.update_editor(|editor, window, cx| {
 2922        editor.delete_to_previous_word_start(
 2923            &DeleteToPreviousWordStart {
 2924                ignore_newlines: true,
 2925                ignore_brackets: false,
 2926            },
 2927            window,
 2928            cx,
 2929        );
 2930    });
 2931    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2932
 2933    cx.update_editor(|editor, window, cx| {
 2934        editor.delete_to_previous_word_start(
 2935            &DeleteToPreviousWordStart {
 2936                ignore_newlines: true,
 2937                ignore_brackets: false,
 2938            },
 2939            window,
 2940            cx,
 2941        );
 2942    });
 2943    cx.assert_editor_state(r#"ˇCOMMENT");"#);
 2944
 2945    cx.update_editor(|editor, window, cx| {
 2946        editor.delete_to_next_word_end(
 2947            &DeleteToNextWordEnd {
 2948                ignore_newlines: true,
 2949                ignore_brackets: false,
 2950            },
 2951            window,
 2952            cx,
 2953        );
 2954    });
 2955    // Brackets on the right are not paired anymore, hence deletion does not stop at them
 2956    cx.assert_editor_state(r#"ˇ");"#);
 2957
 2958    cx.update_editor(|editor, window, cx| {
 2959        editor.delete_to_next_word_end(
 2960            &DeleteToNextWordEnd {
 2961                ignore_newlines: true,
 2962                ignore_brackets: false,
 2963            },
 2964            window,
 2965            cx,
 2966        );
 2967    });
 2968    cx.assert_editor_state(r#"ˇ"#);
 2969
 2970    cx.update_editor(|editor, window, cx| {
 2971        editor.delete_to_next_word_end(
 2972            &DeleteToNextWordEnd {
 2973                ignore_newlines: true,
 2974                ignore_brackets: false,
 2975            },
 2976            window,
 2977            cx,
 2978        );
 2979    });
 2980    cx.assert_editor_state(r#"ˇ"#);
 2981
 2982    cx.set_state(r#"macro!("// ˇCOMMENT");"#);
 2983    cx.update_editor(|editor, window, cx| {
 2984        editor.delete_to_previous_word_start(
 2985            &DeleteToPreviousWordStart {
 2986                ignore_newlines: true,
 2987                ignore_brackets: true,
 2988            },
 2989            window,
 2990            cx,
 2991        );
 2992    });
 2993    cx.assert_editor_state(r#"macroˇCOMMENT");"#);
 2994}
 2995
 2996#[gpui::test]
 2997fn test_delete_to_previous_word_start_or_newline(cx: &mut TestAppContext) {
 2998    init_test(cx, |_| {});
 2999
 3000    let editor = cx.add_window(|window, cx| {
 3001        let buffer = MultiBuffer::build_simple("one\n2\nthree\n4", cx);
 3002        build_editor(buffer, window, cx)
 3003    });
 3004    let del_to_prev_word_start = DeleteToPreviousWordStart {
 3005        ignore_newlines: false,
 3006        ignore_brackets: false,
 3007    };
 3008    let del_to_prev_word_start_ignore_newlines = DeleteToPreviousWordStart {
 3009        ignore_newlines: true,
 3010        ignore_brackets: false,
 3011    };
 3012
 3013    _ = editor.update(cx, |editor, window, cx| {
 3014        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3015            s.select_display_ranges([
 3016                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1)
 3017            ])
 3018        });
 3019        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3020        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree\n");
 3021        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3022        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\nthree");
 3023        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3024        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2\n");
 3025        editor.delete_to_previous_word_start(&del_to_prev_word_start, window, cx);
 3026        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n2");
 3027        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3028        assert_eq!(editor.buffer.read(cx).read(cx).text(), "one\n");
 3029        editor.delete_to_previous_word_start(&del_to_prev_word_start_ignore_newlines, window, cx);
 3030        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3031    });
 3032}
 3033
 3034#[gpui::test]
 3035fn test_delete_to_next_word_end_or_newline(cx: &mut TestAppContext) {
 3036    init_test(cx, |_| {});
 3037
 3038    let editor = cx.add_window(|window, cx| {
 3039        let buffer = MultiBuffer::build_simple("\none\n   two\nthree\n   four", cx);
 3040        build_editor(buffer, window, cx)
 3041    });
 3042    let del_to_next_word_end = DeleteToNextWordEnd {
 3043        ignore_newlines: false,
 3044        ignore_brackets: false,
 3045    };
 3046    let del_to_next_word_end_ignore_newlines = DeleteToNextWordEnd {
 3047        ignore_newlines: true,
 3048        ignore_brackets: false,
 3049    };
 3050
 3051    _ = editor.update(cx, |editor, window, cx| {
 3052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3053            s.select_display_ranges([
 3054                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)
 3055            ])
 3056        });
 3057        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3058        assert_eq!(
 3059            editor.buffer.read(cx).read(cx).text(),
 3060            "one\n   two\nthree\n   four"
 3061        );
 3062        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3063        assert_eq!(
 3064            editor.buffer.read(cx).read(cx).text(),
 3065            "\n   two\nthree\n   four"
 3066        );
 3067        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3068        assert_eq!(
 3069            editor.buffer.read(cx).read(cx).text(),
 3070            "two\nthree\n   four"
 3071        );
 3072        editor.delete_to_next_word_end(&del_to_next_word_end, window, cx);
 3073        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\nthree\n   four");
 3074        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3075        assert_eq!(editor.buffer.read(cx).read(cx).text(), "\n   four");
 3076        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3077        assert_eq!(editor.buffer.read(cx).read(cx).text(), "four");
 3078        editor.delete_to_next_word_end(&del_to_next_word_end_ignore_newlines, window, cx);
 3079        assert_eq!(editor.buffer.read(cx).read(cx).text(), "");
 3080    });
 3081}
 3082
 3083#[gpui::test]
 3084fn test_newline(cx: &mut TestAppContext) {
 3085    init_test(cx, |_| {});
 3086
 3087    let editor = cx.add_window(|window, cx| {
 3088        let buffer = MultiBuffer::build_simple("aaaa\n    bbbb\n", cx);
 3089        build_editor(buffer, window, cx)
 3090    });
 3091
 3092    _ = editor.update(cx, |editor, window, cx| {
 3093        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3094            s.select_display_ranges([
 3095                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 3096                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 3097                DisplayPoint::new(DisplayRow(1), 6)..DisplayPoint::new(DisplayRow(1), 6),
 3098            ])
 3099        });
 3100
 3101        editor.newline(&Newline, window, cx);
 3102        assert_eq!(editor.text(cx), "aa\naa\n  \n    bb\n    bb\n");
 3103    });
 3104}
 3105
 3106#[gpui::test]
 3107async fn test_newline_yaml(cx: &mut TestAppContext) {
 3108    init_test(cx, |_| {});
 3109
 3110    let mut cx = EditorTestContext::new(cx).await;
 3111    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 3112    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 3113
 3114    // Object (between 2 fields)
 3115    cx.set_state(indoc! {"
 3116    test:ˇ
 3117    hello: bye"});
 3118    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3119    cx.assert_editor_state(indoc! {"
 3120    test:
 3121        ˇ
 3122    hello: bye"});
 3123
 3124    // Object (first and single line)
 3125    cx.set_state(indoc! {"
 3126    test:ˇ"});
 3127    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3128    cx.assert_editor_state(indoc! {"
 3129    test:
 3130        ˇ"});
 3131
 3132    // Array with objects (after first element)
 3133    cx.set_state(indoc! {"
 3134    test:
 3135        - foo: barˇ"});
 3136    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3137    cx.assert_editor_state(indoc! {"
 3138    test:
 3139        - foo: bar
 3140        ˇ"});
 3141
 3142    // Array with objects and comment
 3143    cx.set_state(indoc! {"
 3144    test:
 3145        - foo: bar
 3146        - bar: # testˇ"});
 3147    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3148    cx.assert_editor_state(indoc! {"
 3149    test:
 3150        - foo: bar
 3151        - bar: # test
 3152            ˇ"});
 3153
 3154    // Array with objects (after second element)
 3155    cx.set_state(indoc! {"
 3156    test:
 3157        - foo: bar
 3158        - bar: fooˇ"});
 3159    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3160    cx.assert_editor_state(indoc! {"
 3161    test:
 3162        - foo: bar
 3163        - bar: foo
 3164        ˇ"});
 3165
 3166    // Array with strings (after first element)
 3167    cx.set_state(indoc! {"
 3168    test:
 3169        - fooˇ"});
 3170    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3171    cx.assert_editor_state(indoc! {"
 3172    test:
 3173        - foo
 3174        ˇ"});
 3175}
 3176
 3177#[gpui::test]
 3178fn test_newline_with_old_selections(cx: &mut TestAppContext) {
 3179    init_test(cx, |_| {});
 3180
 3181    let editor = cx.add_window(|window, cx| {
 3182        let buffer = MultiBuffer::build_simple(
 3183            "
 3184                a
 3185                b(
 3186                    X
 3187                )
 3188                c(
 3189                    X
 3190                )
 3191            "
 3192            .unindent()
 3193            .as_str(),
 3194            cx,
 3195        );
 3196        let mut editor = build_editor(buffer, window, cx);
 3197        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3198            s.select_ranges([
 3199                Point::new(2, 4)..Point::new(2, 5),
 3200                Point::new(5, 4)..Point::new(5, 5),
 3201            ])
 3202        });
 3203        editor
 3204    });
 3205
 3206    _ = editor.update(cx, |editor, window, cx| {
 3207        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3208        editor.buffer.update(cx, |buffer, cx| {
 3209            buffer.edit(
 3210                [
 3211                    (Point::new(1, 2)..Point::new(3, 0), ""),
 3212                    (Point::new(4, 2)..Point::new(6, 0), ""),
 3213                ],
 3214                None,
 3215                cx,
 3216            );
 3217            assert_eq!(
 3218                buffer.read(cx).text(),
 3219                "
 3220                    a
 3221                    b()
 3222                    c()
 3223                "
 3224                .unindent()
 3225            );
 3226        });
 3227        assert_eq!(
 3228            editor.selections.ranges(&editor.display_snapshot(cx)),
 3229            &[
 3230                Point::new(1, 2)..Point::new(1, 2),
 3231                Point::new(2, 2)..Point::new(2, 2),
 3232            ],
 3233        );
 3234
 3235        editor.newline(&Newline, window, cx);
 3236        assert_eq!(
 3237            editor.text(cx),
 3238            "
 3239                a
 3240                b(
 3241                )
 3242                c(
 3243                )
 3244            "
 3245            .unindent()
 3246        );
 3247
 3248        // The selections are moved after the inserted newlines
 3249        assert_eq!(
 3250            editor.selections.ranges(&editor.display_snapshot(cx)),
 3251            &[
 3252                Point::new(2, 0)..Point::new(2, 0),
 3253                Point::new(4, 0)..Point::new(4, 0),
 3254            ],
 3255        );
 3256    });
 3257}
 3258
 3259#[gpui::test]
 3260async fn test_newline_above(cx: &mut TestAppContext) {
 3261    init_test(cx, |settings| {
 3262        settings.defaults.tab_size = NonZeroU32::new(4)
 3263    });
 3264
 3265    let language = Arc::new(
 3266        Language::new(
 3267            LanguageConfig::default(),
 3268            Some(tree_sitter_rust::LANGUAGE.into()),
 3269        )
 3270        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3271        .unwrap(),
 3272    );
 3273
 3274    let mut cx = EditorTestContext::new(cx).await;
 3275    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3276    cx.set_state(indoc! {"
 3277        const a: ˇA = (
 3278 3279                «const_functionˇ»(ˇ),
 3280                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3281 3282        ˇ);ˇ
 3283    "});
 3284
 3285    cx.update_editor(|e, window, cx| e.newline_above(&NewlineAbove, window, cx));
 3286    cx.assert_editor_state(indoc! {"
 3287        ˇ
 3288        const a: A = (
 3289            ˇ
 3290            (
 3291                ˇ
 3292                ˇ
 3293                const_function(),
 3294                ˇ
 3295                ˇ
 3296                ˇ
 3297                ˇ
 3298                something_else,
 3299                ˇ
 3300            )
 3301            ˇ
 3302            ˇ
 3303        );
 3304    "});
 3305}
 3306
 3307#[gpui::test]
 3308async fn test_newline_below(cx: &mut TestAppContext) {
 3309    init_test(cx, |settings| {
 3310        settings.defaults.tab_size = NonZeroU32::new(4)
 3311    });
 3312
 3313    let language = Arc::new(
 3314        Language::new(
 3315            LanguageConfig::default(),
 3316            Some(tree_sitter_rust::LANGUAGE.into()),
 3317        )
 3318        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3319        .unwrap(),
 3320    );
 3321
 3322    let mut cx = EditorTestContext::new(cx).await;
 3323    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3324    cx.set_state(indoc! {"
 3325        const a: ˇA = (
 3326 3327                «const_functionˇ»(ˇ),
 3328                so«mˇ»et«hˇ»ing_ˇelse,ˇ
 3329 3330        ˇ);ˇ
 3331    "});
 3332
 3333    cx.update_editor(|e, window, cx| e.newline_below(&NewlineBelow, window, cx));
 3334    cx.assert_editor_state(indoc! {"
 3335        const a: A = (
 3336            ˇ
 3337            (
 3338                ˇ
 3339                const_function(),
 3340                ˇ
 3341                ˇ
 3342                something_else,
 3343                ˇ
 3344                ˇ
 3345                ˇ
 3346                ˇ
 3347            )
 3348            ˇ
 3349        );
 3350        ˇ
 3351        ˇ
 3352    "});
 3353}
 3354
 3355#[gpui::test]
 3356async fn test_newline_comments(cx: &mut TestAppContext) {
 3357    init_test(cx, |settings| {
 3358        settings.defaults.tab_size = NonZeroU32::new(4)
 3359    });
 3360
 3361    let language = Arc::new(Language::new(
 3362        LanguageConfig {
 3363            line_comments: vec!["// ".into()],
 3364            ..LanguageConfig::default()
 3365        },
 3366        None,
 3367    ));
 3368    {
 3369        let mut cx = EditorTestContext::new(cx).await;
 3370        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3371        cx.set_state(indoc! {"
 3372        // Fooˇ
 3373    "});
 3374
 3375        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3376        cx.assert_editor_state(indoc! {"
 3377        // Foo
 3378        // ˇ
 3379    "});
 3380        // Ensure that we add comment prefix when existing line contains space
 3381        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3382        cx.assert_editor_state(
 3383            indoc! {"
 3384        // Foo
 3385        //s
 3386        // ˇ
 3387    "}
 3388            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3389            .as_str(),
 3390        );
 3391        // Ensure that we add comment prefix when existing line does not contain space
 3392        cx.set_state(indoc! {"
 3393        // Foo
 3394        //ˇ
 3395    "});
 3396        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3397        cx.assert_editor_state(indoc! {"
 3398        // Foo
 3399        //
 3400        // ˇ
 3401    "});
 3402        // Ensure that if cursor is before the comment start, we do not actually insert a comment prefix.
 3403        cx.set_state(indoc! {"
 3404        ˇ// Foo
 3405    "});
 3406        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3407        cx.assert_editor_state(indoc! {"
 3408
 3409        ˇ// Foo
 3410    "});
 3411    }
 3412    // Ensure that comment continuations can be disabled.
 3413    update_test_language_settings(cx, |settings| {
 3414        settings.defaults.extend_comment_on_newline = Some(false);
 3415    });
 3416    let mut cx = EditorTestContext::new(cx).await;
 3417    cx.set_state(indoc! {"
 3418        // Fooˇ
 3419    "});
 3420    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3421    cx.assert_editor_state(indoc! {"
 3422        // Foo
 3423        ˇ
 3424    "});
 3425}
 3426
 3427#[gpui::test]
 3428async fn test_newline_comments_with_multiple_delimiters(cx: &mut TestAppContext) {
 3429    init_test(cx, |settings| {
 3430        settings.defaults.tab_size = NonZeroU32::new(4)
 3431    });
 3432
 3433    let language = Arc::new(Language::new(
 3434        LanguageConfig {
 3435            line_comments: vec!["// ".into(), "/// ".into()],
 3436            ..LanguageConfig::default()
 3437        },
 3438        None,
 3439    ));
 3440    {
 3441        let mut cx = EditorTestContext::new(cx).await;
 3442        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3443        cx.set_state(indoc! {"
 3444        //ˇ
 3445    "});
 3446        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3447        cx.assert_editor_state(indoc! {"
 3448        //
 3449        // ˇ
 3450    "});
 3451
 3452        cx.set_state(indoc! {"
 3453        ///ˇ
 3454    "});
 3455        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3456        cx.assert_editor_state(indoc! {"
 3457        ///
 3458        /// ˇ
 3459    "});
 3460    }
 3461}
 3462
 3463#[gpui::test]
 3464async fn test_newline_documentation_comments(cx: &mut TestAppContext) {
 3465    init_test(cx, |settings| {
 3466        settings.defaults.tab_size = NonZeroU32::new(4)
 3467    });
 3468
 3469    let language = Arc::new(
 3470        Language::new(
 3471            LanguageConfig {
 3472                documentation_comment: Some(language::BlockCommentConfig {
 3473                    start: "/**".into(),
 3474                    end: "*/".into(),
 3475                    prefix: "* ".into(),
 3476                    tab_size: 1,
 3477                }),
 3478
 3479                ..LanguageConfig::default()
 3480            },
 3481            Some(tree_sitter_rust::LANGUAGE.into()),
 3482        )
 3483        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 3484        .unwrap(),
 3485    );
 3486
 3487    {
 3488        let mut cx = EditorTestContext::new(cx).await;
 3489        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3490        cx.set_state(indoc! {"
 3491        /**ˇ
 3492    "});
 3493
 3494        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3495        cx.assert_editor_state(indoc! {"
 3496        /**
 3497         * ˇ
 3498    "});
 3499        // Ensure that if cursor is before the comment start,
 3500        // we do not actually insert a comment prefix.
 3501        cx.set_state(indoc! {"
 3502        ˇ/**
 3503    "});
 3504        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3505        cx.assert_editor_state(indoc! {"
 3506
 3507        ˇ/**
 3508    "});
 3509        // Ensure that if cursor is between it doesn't add comment prefix.
 3510        cx.set_state(indoc! {"
 3511        /*ˇ*
 3512    "});
 3513        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3514        cx.assert_editor_state(indoc! {"
 3515        /*
 3516        ˇ*
 3517    "});
 3518        // Ensure that if suffix exists on same line after cursor it adds new line.
 3519        cx.set_state(indoc! {"
 3520        /**ˇ*/
 3521    "});
 3522        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3523        cx.assert_editor_state(indoc! {"
 3524        /**
 3525         * ˇ
 3526         */
 3527    "});
 3528        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3529        cx.set_state(indoc! {"
 3530        /**ˇ */
 3531    "});
 3532        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3533        cx.assert_editor_state(indoc! {"
 3534        /**
 3535         * ˇ
 3536         */
 3537    "});
 3538        // Ensure that if suffix exists on same line after cursor with space it adds new line.
 3539        cx.set_state(indoc! {"
 3540        /** ˇ*/
 3541    "});
 3542        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3543        cx.assert_editor_state(
 3544            indoc! {"
 3545        /**s
 3546         * ˇ
 3547         */
 3548    "}
 3549            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3550            .as_str(),
 3551        );
 3552        // Ensure that delimiter space is preserved when newline on already
 3553        // spaced delimiter.
 3554        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3555        cx.assert_editor_state(
 3556            indoc! {"
 3557        /**s
 3558         *s
 3559         * ˇ
 3560         */
 3561    "}
 3562            .replace("s", " ") // s is used as space placeholder to prevent format on save
 3563            .as_str(),
 3564        );
 3565        // Ensure that delimiter space is preserved when space is not
 3566        // on existing delimiter.
 3567        cx.set_state(indoc! {"
 3568        /**
 3569 3570         */
 3571    "});
 3572        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3573        cx.assert_editor_state(indoc! {"
 3574        /**
 3575         *
 3576         * ˇ
 3577         */
 3578    "});
 3579        // Ensure that if suffix exists on same line after cursor it
 3580        // doesn't add extra new line if prefix is not on same line.
 3581        cx.set_state(indoc! {"
 3582        /**
 3583        ˇ*/
 3584    "});
 3585        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3586        cx.assert_editor_state(indoc! {"
 3587        /**
 3588
 3589        ˇ*/
 3590    "});
 3591        // Ensure that it detects suffix after existing prefix.
 3592        cx.set_state(indoc! {"
 3593        /**ˇ/
 3594    "});
 3595        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3596        cx.assert_editor_state(indoc! {"
 3597        /**
 3598        ˇ/
 3599    "});
 3600        // Ensure that if suffix exists on same line before
 3601        // cursor it does not add comment prefix.
 3602        cx.set_state(indoc! {"
 3603        /** */ˇ
 3604    "});
 3605        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3606        cx.assert_editor_state(indoc! {"
 3607        /** */
 3608        ˇ
 3609    "});
 3610        // Ensure that if suffix exists on same line before
 3611        // cursor it does not add comment prefix.
 3612        cx.set_state(indoc! {"
 3613        /**
 3614         *
 3615         */ˇ
 3616    "});
 3617        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3618        cx.assert_editor_state(indoc! {"
 3619        /**
 3620         *
 3621         */
 3622         ˇ
 3623    "});
 3624
 3625        // Ensure that inline comment followed by code
 3626        // doesn't add comment prefix on newline
 3627        cx.set_state(indoc! {"
 3628        /** */ textˇ
 3629    "});
 3630        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3631        cx.assert_editor_state(indoc! {"
 3632        /** */ text
 3633        ˇ
 3634    "});
 3635
 3636        // Ensure that text after comment end tag
 3637        // doesn't add comment prefix on newline
 3638        cx.set_state(indoc! {"
 3639        /**
 3640         *
 3641         */ˇtext
 3642    "});
 3643        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3644        cx.assert_editor_state(indoc! {"
 3645        /**
 3646         *
 3647         */
 3648         ˇtext
 3649    "});
 3650
 3651        // Ensure if not comment block it doesn't
 3652        // add comment prefix on newline
 3653        cx.set_state(indoc! {"
 3654        * textˇ
 3655    "});
 3656        cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3657        cx.assert_editor_state(indoc! {"
 3658        * text
 3659        ˇ
 3660    "});
 3661    }
 3662    // Ensure that comment continuations can be disabled.
 3663    update_test_language_settings(cx, |settings| {
 3664        settings.defaults.extend_comment_on_newline = Some(false);
 3665    });
 3666    let mut cx = EditorTestContext::new(cx).await;
 3667    cx.set_state(indoc! {"
 3668        /**ˇ
 3669    "});
 3670    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3671    cx.assert_editor_state(indoc! {"
 3672        /**
 3673        ˇ
 3674    "});
 3675}
 3676
 3677#[gpui::test]
 3678async fn test_newline_comments_with_block_comment(cx: &mut TestAppContext) {
 3679    init_test(cx, |settings| {
 3680        settings.defaults.tab_size = NonZeroU32::new(4)
 3681    });
 3682
 3683    let lua_language = Arc::new(Language::new(
 3684        LanguageConfig {
 3685            line_comments: vec!["--".into()],
 3686            block_comment: Some(language::BlockCommentConfig {
 3687                start: "--[[".into(),
 3688                prefix: "".into(),
 3689                end: "]]".into(),
 3690                tab_size: 0,
 3691            }),
 3692            ..LanguageConfig::default()
 3693        },
 3694        None,
 3695    ));
 3696
 3697    let mut cx = EditorTestContext::new(cx).await;
 3698    cx.update_buffer(|buffer, cx| buffer.set_language(Some(lua_language), cx));
 3699
 3700    // Line with line comment should extend
 3701    cx.set_state(indoc! {"
 3702        --ˇ
 3703    "});
 3704    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3705    cx.assert_editor_state(indoc! {"
 3706        --
 3707        --ˇ
 3708    "});
 3709
 3710    // Line with block comment that matches line comment should not extend
 3711    cx.set_state(indoc! {"
 3712        --[[ˇ
 3713    "});
 3714    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
 3715    cx.assert_editor_state(indoc! {"
 3716        --[[
 3717        ˇ
 3718    "});
 3719}
 3720
 3721#[gpui::test]
 3722fn test_insert_with_old_selections(cx: &mut TestAppContext) {
 3723    init_test(cx, |_| {});
 3724
 3725    let editor = cx.add_window(|window, cx| {
 3726        let buffer = MultiBuffer::build_simple("a( X ), b( Y ), c( Z )", cx);
 3727        let mut editor = build_editor(buffer, window, cx);
 3728        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 3729            s.select_ranges([
 3730                MultiBufferOffset(3)..MultiBufferOffset(4),
 3731                MultiBufferOffset(11)..MultiBufferOffset(12),
 3732                MultiBufferOffset(19)..MultiBufferOffset(20),
 3733            ])
 3734        });
 3735        editor
 3736    });
 3737
 3738    _ = editor.update(cx, |editor, window, cx| {
 3739        // Edit the buffer directly, deleting ranges surrounding the editor's selections
 3740        editor.buffer.update(cx, |buffer, cx| {
 3741            buffer.edit(
 3742                [
 3743                    (MultiBufferOffset(2)..MultiBufferOffset(5), ""),
 3744                    (MultiBufferOffset(10)..MultiBufferOffset(13), ""),
 3745                    (MultiBufferOffset(18)..MultiBufferOffset(21), ""),
 3746                ],
 3747                None,
 3748                cx,
 3749            );
 3750            assert_eq!(buffer.read(cx).text(), "a(), b(), c()".unindent());
 3751        });
 3752        assert_eq!(
 3753            editor.selections.ranges(&editor.display_snapshot(cx)),
 3754            &[
 3755                MultiBufferOffset(2)..MultiBufferOffset(2),
 3756                MultiBufferOffset(7)..MultiBufferOffset(7),
 3757                MultiBufferOffset(12)..MultiBufferOffset(12)
 3758            ],
 3759        );
 3760
 3761        editor.insert("Z", window, cx);
 3762        assert_eq!(editor.text(cx), "a(Z), b(Z), c(Z)");
 3763
 3764        // The selections are moved after the inserted characters
 3765        assert_eq!(
 3766            editor.selections.ranges(&editor.display_snapshot(cx)),
 3767            &[
 3768                MultiBufferOffset(3)..MultiBufferOffset(3),
 3769                MultiBufferOffset(9)..MultiBufferOffset(9),
 3770                MultiBufferOffset(15)..MultiBufferOffset(15)
 3771            ],
 3772        );
 3773    });
 3774}
 3775
 3776#[gpui::test]
 3777async fn test_tab(cx: &mut TestAppContext) {
 3778    init_test(cx, |settings| {
 3779        settings.defaults.tab_size = NonZeroU32::new(3)
 3780    });
 3781
 3782    let mut cx = EditorTestContext::new(cx).await;
 3783    cx.set_state(indoc! {"
 3784        ˇabˇc
 3785        ˇ🏀ˇ🏀ˇefg
 3786 3787    "});
 3788    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3789    cx.assert_editor_state(indoc! {"
 3790           ˇab ˇc
 3791           ˇ🏀  ˇ🏀  ˇefg
 3792        d  ˇ
 3793    "});
 3794
 3795    cx.set_state(indoc! {"
 3796        a
 3797        «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3798    "});
 3799    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3800    cx.assert_editor_state(indoc! {"
 3801        a
 3802           «🏀ˇ»🏀«🏀ˇ»🏀«🏀ˇ»
 3803    "});
 3804}
 3805
 3806#[gpui::test]
 3807async fn test_tab_in_leading_whitespace_auto_indents_lines(cx: &mut TestAppContext) {
 3808    init_test(cx, |_| {});
 3809
 3810    let mut cx = EditorTestContext::new(cx).await;
 3811    let language = Arc::new(
 3812        Language::new(
 3813            LanguageConfig::default(),
 3814            Some(tree_sitter_rust::LANGUAGE.into()),
 3815        )
 3816        .with_indents_query(r#"(_ "(" ")" @end) @indent"#)
 3817        .unwrap(),
 3818    );
 3819    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 3820
 3821    // test when all cursors are not at suggested indent
 3822    // then simply move to their suggested indent location
 3823    cx.set_state(indoc! {"
 3824        const a: B = (
 3825            c(
 3826        ˇ
 3827        ˇ    )
 3828        );
 3829    "});
 3830    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3831    cx.assert_editor_state(indoc! {"
 3832        const a: B = (
 3833            c(
 3834                ˇ
 3835            ˇ)
 3836        );
 3837    "});
 3838
 3839    // test cursor already at suggested indent not moving when
 3840    // other cursors are yet to reach their suggested indents
 3841    cx.set_state(indoc! {"
 3842        ˇ
 3843        const a: B = (
 3844            c(
 3845                d(
 3846        ˇ
 3847                )
 3848        ˇ
 3849        ˇ    )
 3850        );
 3851    "});
 3852    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3853    cx.assert_editor_state(indoc! {"
 3854        ˇ
 3855        const a: B = (
 3856            c(
 3857                d(
 3858                    ˇ
 3859                )
 3860                ˇ
 3861            ˇ)
 3862        );
 3863    "});
 3864    // test when all cursors are at suggested indent then tab is inserted
 3865    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3866    cx.assert_editor_state(indoc! {"
 3867            ˇ
 3868        const a: B = (
 3869            c(
 3870                d(
 3871                        ˇ
 3872                )
 3873                    ˇ
 3874                ˇ)
 3875        );
 3876    "});
 3877
 3878    // test when current indent is less than suggested indent,
 3879    // we adjust line to match suggested indent and move cursor to it
 3880    //
 3881    // when no other cursor is at word boundary, all of them should move
 3882    cx.set_state(indoc! {"
 3883        const a: B = (
 3884            c(
 3885                d(
 3886        ˇ
 3887        ˇ   )
 3888        ˇ   )
 3889        );
 3890    "});
 3891    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3892    cx.assert_editor_state(indoc! {"
 3893        const a: B = (
 3894            c(
 3895                d(
 3896                    ˇ
 3897                ˇ)
 3898            ˇ)
 3899        );
 3900    "});
 3901
 3902    // test when current indent is less than suggested indent,
 3903    // we adjust line to match suggested indent and move cursor to it
 3904    //
 3905    // when some other cursor is at word boundary, it should not move
 3906    cx.set_state(indoc! {"
 3907        const a: B = (
 3908            c(
 3909                d(
 3910        ˇ
 3911        ˇ   )
 3912           ˇ)
 3913        );
 3914    "});
 3915    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3916    cx.assert_editor_state(indoc! {"
 3917        const a: B = (
 3918            c(
 3919                d(
 3920                    ˇ
 3921                ˇ)
 3922            ˇ)
 3923        );
 3924    "});
 3925
 3926    // test when current indent is more than suggested indent,
 3927    // we just move cursor to current indent instead of suggested indent
 3928    //
 3929    // when no other cursor is at word boundary, all of them should move
 3930    cx.set_state(indoc! {"
 3931        const a: B = (
 3932            c(
 3933                d(
 3934        ˇ
 3935        ˇ                )
 3936        ˇ   )
 3937        );
 3938    "});
 3939    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3940    cx.assert_editor_state(indoc! {"
 3941        const a: B = (
 3942            c(
 3943                d(
 3944                    ˇ
 3945                        ˇ)
 3946            ˇ)
 3947        );
 3948    "});
 3949    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3950    cx.assert_editor_state(indoc! {"
 3951        const a: B = (
 3952            c(
 3953                d(
 3954                        ˇ
 3955                            ˇ)
 3956                ˇ)
 3957        );
 3958    "});
 3959
 3960    // test when current indent is more than suggested indent,
 3961    // we just move cursor to current indent instead of suggested indent
 3962    //
 3963    // when some other cursor is at word boundary, it doesn't move
 3964    cx.set_state(indoc! {"
 3965        const a: B = (
 3966            c(
 3967                d(
 3968        ˇ
 3969        ˇ                )
 3970            ˇ)
 3971        );
 3972    "});
 3973    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3974    cx.assert_editor_state(indoc! {"
 3975        const a: B = (
 3976            c(
 3977                d(
 3978                    ˇ
 3979                        ˇ)
 3980            ˇ)
 3981        );
 3982    "});
 3983
 3984    // handle auto-indent when there are multiple cursors on the same line
 3985    cx.set_state(indoc! {"
 3986        const a: B = (
 3987            c(
 3988        ˇ    ˇ
 3989        ˇ    )
 3990        );
 3991    "});
 3992    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 3993    cx.assert_editor_state(indoc! {"
 3994        const a: B = (
 3995            c(
 3996                ˇ
 3997            ˇ)
 3998        );
 3999    "});
 4000}
 4001
 4002#[gpui::test]
 4003async fn test_tab_with_mixed_whitespace_txt(cx: &mut TestAppContext) {
 4004    init_test(cx, |settings| {
 4005        settings.defaults.tab_size = NonZeroU32::new(3)
 4006    });
 4007
 4008    let mut cx = EditorTestContext::new(cx).await;
 4009    cx.set_state(indoc! {"
 4010         ˇ
 4011        \t ˇ
 4012        \t  ˇ
 4013        \t   ˇ
 4014         \t  \t\t \t      \t\t   \t\t    \t \t ˇ
 4015    "});
 4016
 4017    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4018    cx.assert_editor_state(indoc! {"
 4019           ˇ
 4020        \t   ˇ
 4021        \t   ˇ
 4022        \t      ˇ
 4023         \t  \t\t \t      \t\t   \t\t    \t \t   ˇ
 4024    "});
 4025}
 4026
 4027#[gpui::test]
 4028async fn test_tab_with_mixed_whitespace_rust(cx: &mut TestAppContext) {
 4029    init_test(cx, |settings| {
 4030        settings.defaults.tab_size = NonZeroU32::new(4)
 4031    });
 4032
 4033    let language = Arc::new(
 4034        Language::new(
 4035            LanguageConfig::default(),
 4036            Some(tree_sitter_rust::LANGUAGE.into()),
 4037        )
 4038        .with_indents_query(r#"(_ "{" "}" @end) @indent"#)
 4039        .unwrap(),
 4040    );
 4041
 4042    let mut cx = EditorTestContext::new(cx).await;
 4043    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 4044    cx.set_state(indoc! {"
 4045        fn a() {
 4046            if b {
 4047        \t ˇc
 4048            }
 4049        }
 4050    "});
 4051
 4052    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4053    cx.assert_editor_state(indoc! {"
 4054        fn a() {
 4055            if b {
 4056                ˇc
 4057            }
 4058        }
 4059    "});
 4060}
 4061
 4062#[gpui::test]
 4063async fn test_indent_outdent(cx: &mut TestAppContext) {
 4064    init_test(cx, |settings| {
 4065        settings.defaults.tab_size = NonZeroU32::new(4);
 4066    });
 4067
 4068    let mut cx = EditorTestContext::new(cx).await;
 4069
 4070    cx.set_state(indoc! {"
 4071          «oneˇ» «twoˇ»
 4072        three
 4073         four
 4074    "});
 4075    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4076    cx.assert_editor_state(indoc! {"
 4077            «oneˇ» «twoˇ»
 4078        three
 4079         four
 4080    "});
 4081
 4082    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4083    cx.assert_editor_state(indoc! {"
 4084        «oneˇ» «twoˇ»
 4085        three
 4086         four
 4087    "});
 4088
 4089    // select across line ending
 4090    cx.set_state(indoc! {"
 4091        one two
 4092        t«hree
 4093        ˇ» four
 4094    "});
 4095    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4096    cx.assert_editor_state(indoc! {"
 4097        one two
 4098            t«hree
 4099        ˇ» four
 4100    "});
 4101
 4102    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4103    cx.assert_editor_state(indoc! {"
 4104        one two
 4105        t«hree
 4106        ˇ» four
 4107    "});
 4108
 4109    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4110    cx.set_state(indoc! {"
 4111        one two
 4112        ˇthree
 4113            four
 4114    "});
 4115    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4116    cx.assert_editor_state(indoc! {"
 4117        one two
 4118            ˇthree
 4119            four
 4120    "});
 4121
 4122    cx.set_state(indoc! {"
 4123        one two
 4124        ˇ    three
 4125            four
 4126    "});
 4127    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4128    cx.assert_editor_state(indoc! {"
 4129        one two
 4130        ˇthree
 4131            four
 4132    "});
 4133}
 4134
 4135#[gpui::test]
 4136async fn test_indent_yaml_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4137    // This is a regression test for issue #33761
 4138    init_test(cx, |_| {});
 4139
 4140    let mut cx = EditorTestContext::new(cx).await;
 4141    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4142    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4143
 4144    cx.set_state(
 4145        r#"ˇ#     ingress:
 4146ˇ#         api:
 4147ˇ#             enabled: false
 4148ˇ#             pathType: Prefix
 4149ˇ#           console:
 4150ˇ#               enabled: false
 4151ˇ#               pathType: Prefix
 4152"#,
 4153    );
 4154
 4155    // Press tab to indent all lines
 4156    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4157
 4158    cx.assert_editor_state(
 4159        r#"    ˇ#     ingress:
 4160    ˇ#         api:
 4161    ˇ#             enabled: false
 4162    ˇ#             pathType: Prefix
 4163    ˇ#           console:
 4164    ˇ#               enabled: false
 4165    ˇ#               pathType: Prefix
 4166"#,
 4167    );
 4168}
 4169
 4170#[gpui::test]
 4171async fn test_indent_yaml_non_comments_with_multiple_cursors(cx: &mut TestAppContext) {
 4172    // This is a test to make sure our fix for issue #33761 didn't break anything
 4173    init_test(cx, |_| {});
 4174
 4175    let mut cx = EditorTestContext::new(cx).await;
 4176    let yaml_language = languages::language("yaml", tree_sitter_yaml::LANGUAGE.into());
 4177    cx.update_buffer(|buffer, cx| buffer.set_language(Some(yaml_language), cx));
 4178
 4179    cx.set_state(
 4180        r#"ˇingress:
 4181ˇ  api:
 4182ˇ    enabled: false
 4183ˇ    pathType: Prefix
 4184"#,
 4185    );
 4186
 4187    // Press tab to indent all lines
 4188    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4189
 4190    cx.assert_editor_state(
 4191        r#"ˇingress:
 4192    ˇapi:
 4193        ˇenabled: false
 4194        ˇpathType: Prefix
 4195"#,
 4196    );
 4197}
 4198
 4199#[gpui::test]
 4200async fn test_indent_outdent_with_hard_tabs(cx: &mut TestAppContext) {
 4201    init_test(cx, |settings| {
 4202        settings.defaults.hard_tabs = Some(true);
 4203    });
 4204
 4205    let mut cx = EditorTestContext::new(cx).await;
 4206
 4207    // select two ranges on one line
 4208    cx.set_state(indoc! {"
 4209        «oneˇ» «twoˇ»
 4210        three
 4211        four
 4212    "});
 4213    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4214    cx.assert_editor_state(indoc! {"
 4215        \t«oneˇ» «twoˇ»
 4216        three
 4217        four
 4218    "});
 4219    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4220    cx.assert_editor_state(indoc! {"
 4221        \t\t«oneˇ» «twoˇ»
 4222        three
 4223        four
 4224    "});
 4225    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4226    cx.assert_editor_state(indoc! {"
 4227        \t«oneˇ» «twoˇ»
 4228        three
 4229        four
 4230    "});
 4231    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4232    cx.assert_editor_state(indoc! {"
 4233        «oneˇ» «twoˇ»
 4234        three
 4235        four
 4236    "});
 4237
 4238    // select across a line ending
 4239    cx.set_state(indoc! {"
 4240        one two
 4241        t«hree
 4242        ˇ»four
 4243    "});
 4244    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4245    cx.assert_editor_state(indoc! {"
 4246        one two
 4247        \tt«hree
 4248        ˇ»four
 4249    "});
 4250    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4251    cx.assert_editor_state(indoc! {"
 4252        one two
 4253        \t\tt«hree
 4254        ˇ»four
 4255    "});
 4256    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4257    cx.assert_editor_state(indoc! {"
 4258        one two
 4259        \tt«hree
 4260        ˇ»four
 4261    "});
 4262    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4263    cx.assert_editor_state(indoc! {"
 4264        one two
 4265        t«hree
 4266        ˇ»four
 4267    "});
 4268
 4269    // Ensure that indenting/outdenting works when the cursor is at column 0.
 4270    cx.set_state(indoc! {"
 4271        one two
 4272        ˇthree
 4273        four
 4274    "});
 4275    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4276    cx.assert_editor_state(indoc! {"
 4277        one two
 4278        ˇthree
 4279        four
 4280    "});
 4281    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
 4282    cx.assert_editor_state(indoc! {"
 4283        one two
 4284        \tˇthree
 4285        four
 4286    "});
 4287    cx.update_editor(|e, window, cx| e.backtab(&Backtab, window, cx));
 4288    cx.assert_editor_state(indoc! {"
 4289        one two
 4290        ˇthree
 4291        four
 4292    "});
 4293}
 4294
 4295#[gpui::test]
 4296fn test_indent_outdent_with_excerpts(cx: &mut TestAppContext) {
 4297    init_test(cx, |settings| {
 4298        settings.languages.0.extend([
 4299            (
 4300                "TOML".into(),
 4301                LanguageSettingsContent {
 4302                    tab_size: NonZeroU32::new(2),
 4303                    ..Default::default()
 4304                },
 4305            ),
 4306            (
 4307                "Rust".into(),
 4308                LanguageSettingsContent {
 4309                    tab_size: NonZeroU32::new(4),
 4310                    ..Default::default()
 4311                },
 4312            ),
 4313        ]);
 4314    });
 4315
 4316    let toml_language = Arc::new(Language::new(
 4317        LanguageConfig {
 4318            name: "TOML".into(),
 4319            ..Default::default()
 4320        },
 4321        None,
 4322    ));
 4323    let rust_language = Arc::new(Language::new(
 4324        LanguageConfig {
 4325            name: "Rust".into(),
 4326            ..Default::default()
 4327        },
 4328        None,
 4329    ));
 4330
 4331    let toml_buffer =
 4332        cx.new(|cx| Buffer::local("a = 1\nb = 2\n", cx).with_language(toml_language, cx));
 4333    let rust_buffer =
 4334        cx.new(|cx| Buffer::local("const c: usize = 3;\n", cx).with_language(rust_language, cx));
 4335    let multibuffer = cx.new(|cx| {
 4336        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4337        multibuffer.push_excerpts(
 4338            toml_buffer.clone(),
 4339            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4340            cx,
 4341        );
 4342        multibuffer.push_excerpts(
 4343            rust_buffer.clone(),
 4344            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4345            cx,
 4346        );
 4347        multibuffer
 4348    });
 4349
 4350    cx.add_window(|window, cx| {
 4351        let mut editor = build_editor(multibuffer, window, cx);
 4352
 4353        assert_eq!(
 4354            editor.text(cx),
 4355            indoc! {"
 4356                a = 1
 4357                b = 2
 4358
 4359                const c: usize = 3;
 4360            "}
 4361        );
 4362
 4363        select_ranges(
 4364            &mut editor,
 4365            indoc! {"
 4366                «aˇ» = 1
 4367                b = 2
 4368
 4369                «const c:ˇ» usize = 3;
 4370            "},
 4371            window,
 4372            cx,
 4373        );
 4374
 4375        editor.tab(&Tab, window, cx);
 4376        assert_text_with_selections(
 4377            &mut editor,
 4378            indoc! {"
 4379                  «aˇ» = 1
 4380                b = 2
 4381
 4382                    «const c:ˇ» usize = 3;
 4383            "},
 4384            cx,
 4385        );
 4386        editor.backtab(&Backtab, window, cx);
 4387        assert_text_with_selections(
 4388            &mut editor,
 4389            indoc! {"
 4390                «aˇ» = 1
 4391                b = 2
 4392
 4393                «const c:ˇ» usize = 3;
 4394            "},
 4395            cx,
 4396        );
 4397
 4398        editor
 4399    });
 4400}
 4401
 4402#[gpui::test]
 4403async fn test_backspace(cx: &mut TestAppContext) {
 4404    init_test(cx, |_| {});
 4405
 4406    let mut cx = EditorTestContext::new(cx).await;
 4407
 4408    // Basic backspace
 4409    cx.set_state(indoc! {"
 4410        onˇe two three
 4411        fou«rˇ» five six
 4412        seven «ˇeight nine
 4413        »ten
 4414    "});
 4415    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4416    cx.assert_editor_state(indoc! {"
 4417        oˇe two three
 4418        fouˇ five six
 4419        seven ˇten
 4420    "});
 4421
 4422    // Test backspace inside and around indents
 4423    cx.set_state(indoc! {"
 4424        zero
 4425            ˇone
 4426                ˇtwo
 4427            ˇ ˇ ˇ  three
 4428        ˇ  ˇ  four
 4429    "});
 4430    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4431    cx.assert_editor_state(indoc! {"
 4432        zero
 4433        ˇone
 4434            ˇtwo
 4435        ˇ  threeˇ  four
 4436    "});
 4437}
 4438
 4439#[gpui::test]
 4440async fn test_delete(cx: &mut TestAppContext) {
 4441    init_test(cx, |_| {});
 4442
 4443    let mut cx = EditorTestContext::new(cx).await;
 4444    cx.set_state(indoc! {"
 4445        onˇe two three
 4446        fou«rˇ» five six
 4447        seven «ˇeight nine
 4448        »ten
 4449    "});
 4450    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4451    cx.assert_editor_state(indoc! {"
 4452        onˇ two three
 4453        fouˇ five six
 4454        seven ˇten
 4455    "});
 4456}
 4457
 4458#[gpui::test]
 4459fn test_delete_line(cx: &mut TestAppContext) {
 4460    init_test(cx, |_| {});
 4461
 4462    let editor = cx.add_window(|window, cx| {
 4463        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4464        build_editor(buffer, window, cx)
 4465    });
 4466    _ = editor.update(cx, |editor, window, cx| {
 4467        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4468            s.select_display_ranges([
 4469                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4470                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4471                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4472            ])
 4473        });
 4474        editor.delete_line(&DeleteLine, window, cx);
 4475        assert_eq!(editor.display_text(cx), "ghi");
 4476        assert_eq!(
 4477            display_ranges(editor, cx),
 4478            vec![
 4479                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4480                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4481            ]
 4482        );
 4483    });
 4484
 4485    let editor = cx.add_window(|window, cx| {
 4486        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4487        build_editor(buffer, window, cx)
 4488    });
 4489    _ = editor.update(cx, |editor, window, cx| {
 4490        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4491            s.select_display_ranges([
 4492                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4493            ])
 4494        });
 4495        editor.delete_line(&DeleteLine, window, cx);
 4496        assert_eq!(editor.display_text(cx), "ghi\n");
 4497        assert_eq!(
 4498            display_ranges(editor, cx),
 4499            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4500        );
 4501    });
 4502
 4503    let editor = cx.add_window(|window, cx| {
 4504        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4505        build_editor(buffer, window, cx)
 4506    });
 4507    _ = editor.update(cx, |editor, window, cx| {
 4508        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4509            s.select_display_ranges([
 4510                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4511            ])
 4512        });
 4513        editor.delete_line(&DeleteLine, window, cx);
 4514        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4515        assert_eq!(
 4516            display_ranges(editor, cx),
 4517            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4518        );
 4519    });
 4520}
 4521
 4522#[gpui::test]
 4523fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4524    init_test(cx, |_| {});
 4525
 4526    cx.add_window(|window, cx| {
 4527        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4528        let mut editor = build_editor(buffer.clone(), window, cx);
 4529        let buffer = buffer.read(cx).as_singleton().unwrap();
 4530
 4531        assert_eq!(
 4532            editor
 4533                .selections
 4534                .ranges::<Point>(&editor.display_snapshot(cx)),
 4535            &[Point::new(0, 0)..Point::new(0, 0)]
 4536        );
 4537
 4538        // When on single line, replace newline at end by space
 4539        editor.join_lines(&JoinLines, window, cx);
 4540        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4541        assert_eq!(
 4542            editor
 4543                .selections
 4544                .ranges::<Point>(&editor.display_snapshot(cx)),
 4545            &[Point::new(0, 3)..Point::new(0, 3)]
 4546        );
 4547
 4548        // When multiple lines are selected, remove newlines that are spanned by the selection
 4549        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4550            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4551        });
 4552        editor.join_lines(&JoinLines, window, cx);
 4553        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4554        assert_eq!(
 4555            editor
 4556                .selections
 4557                .ranges::<Point>(&editor.display_snapshot(cx)),
 4558            &[Point::new(0, 11)..Point::new(0, 11)]
 4559        );
 4560
 4561        // Undo should be transactional
 4562        editor.undo(&Undo, window, cx);
 4563        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4564        assert_eq!(
 4565            editor
 4566                .selections
 4567                .ranges::<Point>(&editor.display_snapshot(cx)),
 4568            &[Point::new(0, 5)..Point::new(2, 2)]
 4569        );
 4570
 4571        // When joining an empty line don't insert a space
 4572        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4573            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4574        });
 4575        editor.join_lines(&JoinLines, window, cx);
 4576        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4577        assert_eq!(
 4578            editor
 4579                .selections
 4580                .ranges::<Point>(&editor.display_snapshot(cx)),
 4581            [Point::new(2, 3)..Point::new(2, 3)]
 4582        );
 4583
 4584        // We can remove trailing newlines
 4585        editor.join_lines(&JoinLines, window, cx);
 4586        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4587        assert_eq!(
 4588            editor
 4589                .selections
 4590                .ranges::<Point>(&editor.display_snapshot(cx)),
 4591            [Point::new(2, 3)..Point::new(2, 3)]
 4592        );
 4593
 4594        // We don't blow up on the last line
 4595        editor.join_lines(&JoinLines, window, cx);
 4596        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4597        assert_eq!(
 4598            editor
 4599                .selections
 4600                .ranges::<Point>(&editor.display_snapshot(cx)),
 4601            [Point::new(2, 3)..Point::new(2, 3)]
 4602        );
 4603
 4604        // reset to test indentation
 4605        editor.buffer.update(cx, |buffer, cx| {
 4606            buffer.edit(
 4607                [
 4608                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4609                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4610                ],
 4611                None,
 4612                cx,
 4613            )
 4614        });
 4615
 4616        // We remove any leading spaces
 4617        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4618        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4619            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4620        });
 4621        editor.join_lines(&JoinLines, window, cx);
 4622        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4623
 4624        // We don't insert a space for a line containing only spaces
 4625        editor.join_lines(&JoinLines, window, cx);
 4626        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4627
 4628        // We ignore any leading tabs
 4629        editor.join_lines(&JoinLines, window, cx);
 4630        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4631
 4632        editor
 4633    });
 4634}
 4635
 4636#[gpui::test]
 4637fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4638    init_test(cx, |_| {});
 4639
 4640    cx.add_window(|window, cx| {
 4641        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4642        let mut editor = build_editor(buffer.clone(), window, cx);
 4643        let buffer = buffer.read(cx).as_singleton().unwrap();
 4644
 4645        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4646            s.select_ranges([
 4647                Point::new(0, 2)..Point::new(1, 1),
 4648                Point::new(1, 2)..Point::new(1, 2),
 4649                Point::new(3, 1)..Point::new(3, 2),
 4650            ])
 4651        });
 4652
 4653        editor.join_lines(&JoinLines, window, cx);
 4654        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4655
 4656        assert_eq!(
 4657            editor
 4658                .selections
 4659                .ranges::<Point>(&editor.display_snapshot(cx)),
 4660            [
 4661                Point::new(0, 7)..Point::new(0, 7),
 4662                Point::new(1, 3)..Point::new(1, 3)
 4663            ]
 4664        );
 4665        editor
 4666    });
 4667}
 4668
 4669#[gpui::test]
 4670async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4671    init_test(cx, |_| {});
 4672
 4673    let mut cx = EditorTestContext::new(cx).await;
 4674
 4675    let diff_base = r#"
 4676        Line 0
 4677        Line 1
 4678        Line 2
 4679        Line 3
 4680        "#
 4681    .unindent();
 4682
 4683    cx.set_state(
 4684        &r#"
 4685        ˇLine 0
 4686        Line 1
 4687        Line 2
 4688        Line 3
 4689        "#
 4690        .unindent(),
 4691    );
 4692
 4693    cx.set_head_text(&diff_base);
 4694    executor.run_until_parked();
 4695
 4696    // Join lines
 4697    cx.update_editor(|editor, window, cx| {
 4698        editor.join_lines(&JoinLines, window, cx);
 4699    });
 4700    executor.run_until_parked();
 4701
 4702    cx.assert_editor_state(
 4703        &r#"
 4704        Line 0ˇ Line 1
 4705        Line 2
 4706        Line 3
 4707        "#
 4708        .unindent(),
 4709    );
 4710    // Join again
 4711    cx.update_editor(|editor, window, cx| {
 4712        editor.join_lines(&JoinLines, window, cx);
 4713    });
 4714    executor.run_until_parked();
 4715
 4716    cx.assert_editor_state(
 4717        &r#"
 4718        Line 0 Line 1ˇ Line 2
 4719        Line 3
 4720        "#
 4721        .unindent(),
 4722    );
 4723}
 4724
 4725#[gpui::test]
 4726async fn test_custom_newlines_cause_no_false_positive_diffs(
 4727    executor: BackgroundExecutor,
 4728    cx: &mut TestAppContext,
 4729) {
 4730    init_test(cx, |_| {});
 4731    let mut cx = EditorTestContext::new(cx).await;
 4732    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4733    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4734    executor.run_until_parked();
 4735
 4736    cx.update_editor(|editor, window, cx| {
 4737        let snapshot = editor.snapshot(window, cx);
 4738        assert_eq!(
 4739            snapshot
 4740                .buffer_snapshot()
 4741                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4742                .collect::<Vec<_>>(),
 4743            Vec::new(),
 4744            "Should not have any diffs for files with custom newlines"
 4745        );
 4746    });
 4747}
 4748
 4749#[gpui::test]
 4750async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4751    init_test(cx, |_| {});
 4752
 4753    let mut cx = EditorTestContext::new(cx).await;
 4754
 4755    // Test sort_lines_case_insensitive()
 4756    cx.set_state(indoc! {"
 4757        «z
 4758        y
 4759        x
 4760        Z
 4761        Y
 4762        Xˇ»
 4763    "});
 4764    cx.update_editor(|e, window, cx| {
 4765        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4766    });
 4767    cx.assert_editor_state(indoc! {"
 4768        «x
 4769        X
 4770        y
 4771        Y
 4772        z
 4773        Zˇ»
 4774    "});
 4775
 4776    // Test sort_lines_by_length()
 4777    //
 4778    // Demonstrates:
 4779    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4780    // - sort is stable
 4781    cx.set_state(indoc! {"
 4782        «123
 4783        æ
 4784        12
 4785 4786        1
 4787        æˇ»
 4788    "});
 4789    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4790    cx.assert_editor_state(indoc! {"
 4791        «æ
 4792 4793        1
 4794        æ
 4795        12
 4796        123ˇ»
 4797    "});
 4798
 4799    // Test reverse_lines()
 4800    cx.set_state(indoc! {"
 4801        «5
 4802        4
 4803        3
 4804        2
 4805        1ˇ»
 4806    "});
 4807    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4808    cx.assert_editor_state(indoc! {"
 4809        «1
 4810        2
 4811        3
 4812        4
 4813        5ˇ»
 4814    "});
 4815
 4816    // Skip testing shuffle_line()
 4817
 4818    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4819    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4820
 4821    // Don't manipulate when cursor is on single line, but expand the selection
 4822    cx.set_state(indoc! {"
 4823        ddˇdd
 4824        ccc
 4825        bb
 4826        a
 4827    "});
 4828    cx.update_editor(|e, window, cx| {
 4829        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4830    });
 4831    cx.assert_editor_state(indoc! {"
 4832        «ddddˇ»
 4833        ccc
 4834        bb
 4835        a
 4836    "});
 4837
 4838    // Basic manipulate case
 4839    // Start selection moves to column 0
 4840    // End of selection shrinks to fit shorter line
 4841    cx.set_state(indoc! {"
 4842        dd«d
 4843        ccc
 4844        bb
 4845        aaaaaˇ»
 4846    "});
 4847    cx.update_editor(|e, window, cx| {
 4848        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4849    });
 4850    cx.assert_editor_state(indoc! {"
 4851        «aaaaa
 4852        bb
 4853        ccc
 4854        dddˇ»
 4855    "});
 4856
 4857    // Manipulate case with newlines
 4858    cx.set_state(indoc! {"
 4859        dd«d
 4860        ccc
 4861
 4862        bb
 4863        aaaaa
 4864
 4865        ˇ»
 4866    "});
 4867    cx.update_editor(|e, window, cx| {
 4868        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4869    });
 4870    cx.assert_editor_state(indoc! {"
 4871        «
 4872
 4873        aaaaa
 4874        bb
 4875        ccc
 4876        dddˇ»
 4877
 4878    "});
 4879
 4880    // Adding new line
 4881    cx.set_state(indoc! {"
 4882        aa«a
 4883        bbˇ»b
 4884    "});
 4885    cx.update_editor(|e, window, cx| {
 4886        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4887    });
 4888    cx.assert_editor_state(indoc! {"
 4889        «aaa
 4890        bbb
 4891        added_lineˇ»
 4892    "});
 4893
 4894    // Removing line
 4895    cx.set_state(indoc! {"
 4896        aa«a
 4897        bbbˇ»
 4898    "});
 4899    cx.update_editor(|e, window, cx| {
 4900        e.manipulate_immutable_lines(window, cx, |lines| {
 4901            lines.pop();
 4902        })
 4903    });
 4904    cx.assert_editor_state(indoc! {"
 4905        «aaaˇ»
 4906    "});
 4907
 4908    // Removing all lines
 4909    cx.set_state(indoc! {"
 4910        aa«a
 4911        bbbˇ»
 4912    "});
 4913    cx.update_editor(|e, window, cx| {
 4914        e.manipulate_immutable_lines(window, cx, |lines| {
 4915            lines.drain(..);
 4916        })
 4917    });
 4918    cx.assert_editor_state(indoc! {"
 4919        ˇ
 4920    "});
 4921}
 4922
 4923#[gpui::test]
 4924async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4925    init_test(cx, |_| {});
 4926
 4927    let mut cx = EditorTestContext::new(cx).await;
 4928
 4929    // Consider continuous selection as single selection
 4930    cx.set_state(indoc! {"
 4931        Aaa«aa
 4932        cˇ»c«c
 4933        bb
 4934        aaaˇ»aa
 4935    "});
 4936    cx.update_editor(|e, window, cx| {
 4937        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4938    });
 4939    cx.assert_editor_state(indoc! {"
 4940        «Aaaaa
 4941        ccc
 4942        bb
 4943        aaaaaˇ»
 4944    "});
 4945
 4946    cx.set_state(indoc! {"
 4947        Aaa«aa
 4948        cˇ»c«c
 4949        bb
 4950        aaaˇ»aa
 4951    "});
 4952    cx.update_editor(|e, window, cx| {
 4953        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4954    });
 4955    cx.assert_editor_state(indoc! {"
 4956        «Aaaaa
 4957        ccc
 4958        bbˇ»
 4959    "});
 4960
 4961    // Consider non continuous selection as distinct dedup operations
 4962    cx.set_state(indoc! {"
 4963        «aaaaa
 4964        bb
 4965        aaaaa
 4966        aaaaaˇ»
 4967
 4968        aaa«aaˇ»
 4969    "});
 4970    cx.update_editor(|e, window, cx| {
 4971        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4972    });
 4973    cx.assert_editor_state(indoc! {"
 4974        «aaaaa
 4975        bbˇ»
 4976
 4977        «aaaaaˇ»
 4978    "});
 4979}
 4980
 4981#[gpui::test]
 4982async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4983    init_test(cx, |_| {});
 4984
 4985    let mut cx = EditorTestContext::new(cx).await;
 4986
 4987    cx.set_state(indoc! {"
 4988        «Aaa
 4989        aAa
 4990        Aaaˇ»
 4991    "});
 4992    cx.update_editor(|e, window, cx| {
 4993        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4994    });
 4995    cx.assert_editor_state(indoc! {"
 4996        «Aaa
 4997        aAaˇ»
 4998    "});
 4999
 5000    cx.set_state(indoc! {"
 5001        «Aaa
 5002        aAa
 5003        aaAˇ»
 5004    "});
 5005    cx.update_editor(|e, window, cx| {
 5006        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5007    });
 5008    cx.assert_editor_state(indoc! {"
 5009        «Aaaˇ»
 5010    "});
 5011}
 5012
 5013#[gpui::test]
 5014async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5015    init_test(cx, |_| {});
 5016
 5017    let mut cx = EditorTestContext::new(cx).await;
 5018
 5019    let js_language = Arc::new(Language::new(
 5020        LanguageConfig {
 5021            name: "JavaScript".into(),
 5022            wrap_characters: Some(language::WrapCharactersConfig {
 5023                start_prefix: "<".into(),
 5024                start_suffix: ">".into(),
 5025                end_prefix: "</".into(),
 5026                end_suffix: ">".into(),
 5027            }),
 5028            ..LanguageConfig::default()
 5029        },
 5030        None,
 5031    ));
 5032
 5033    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5034
 5035    cx.set_state(indoc! {"
 5036        «testˇ»
 5037    "});
 5038    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5039    cx.assert_editor_state(indoc! {"
 5040        <«ˇ»>test</«ˇ»>
 5041    "});
 5042
 5043    cx.set_state(indoc! {"
 5044        «test
 5045         testˇ»
 5046    "});
 5047    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5048    cx.assert_editor_state(indoc! {"
 5049        <«ˇ»>test
 5050         test</«ˇ»>
 5051    "});
 5052
 5053    cx.set_state(indoc! {"
 5054        teˇst
 5055    "});
 5056    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5057    cx.assert_editor_state(indoc! {"
 5058        te<«ˇ»></«ˇ»>st
 5059    "});
 5060}
 5061
 5062#[gpui::test]
 5063async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5064    init_test(cx, |_| {});
 5065
 5066    let mut cx = EditorTestContext::new(cx).await;
 5067
 5068    let js_language = Arc::new(Language::new(
 5069        LanguageConfig {
 5070            name: "JavaScript".into(),
 5071            wrap_characters: Some(language::WrapCharactersConfig {
 5072                start_prefix: "<".into(),
 5073                start_suffix: ">".into(),
 5074                end_prefix: "</".into(),
 5075                end_suffix: ">".into(),
 5076            }),
 5077            ..LanguageConfig::default()
 5078        },
 5079        None,
 5080    ));
 5081
 5082    cx.update_buffer(|buffer, cx| buffer.set_language(Some(js_language), cx));
 5083
 5084    cx.set_state(indoc! {"
 5085        «testˇ»
 5086        «testˇ» «testˇ»
 5087        «testˇ»
 5088    "});
 5089    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5090    cx.assert_editor_state(indoc! {"
 5091        <«ˇ»>test</«ˇ»>
 5092        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5093        <«ˇ»>test</«ˇ»>
 5094    "});
 5095
 5096    cx.set_state(indoc! {"
 5097        «test
 5098         testˇ»
 5099        «test
 5100         testˇ»
 5101    "});
 5102    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5103    cx.assert_editor_state(indoc! {"
 5104        <«ˇ»>test
 5105         test</«ˇ»>
 5106        <«ˇ»>test
 5107         test</«ˇ»>
 5108    "});
 5109}
 5110
 5111#[gpui::test]
 5112async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5113    init_test(cx, |_| {});
 5114
 5115    let mut cx = EditorTestContext::new(cx).await;
 5116
 5117    let plaintext_language = Arc::new(Language::new(
 5118        LanguageConfig {
 5119            name: "Plain Text".into(),
 5120            ..LanguageConfig::default()
 5121        },
 5122        None,
 5123    ));
 5124
 5125    cx.update_buffer(|buffer, cx| buffer.set_language(Some(plaintext_language), cx));
 5126
 5127    cx.set_state(indoc! {"
 5128        «testˇ»
 5129    "});
 5130    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5131    cx.assert_editor_state(indoc! {"
 5132      «testˇ»
 5133    "});
 5134}
 5135
 5136#[gpui::test]
 5137async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5138    init_test(cx, |_| {});
 5139
 5140    let mut cx = EditorTestContext::new(cx).await;
 5141
 5142    // Manipulate with multiple selections on a single line
 5143    cx.set_state(indoc! {"
 5144        dd«dd
 5145        cˇ»c«c
 5146        bb
 5147        aaaˇ»aa
 5148    "});
 5149    cx.update_editor(|e, window, cx| {
 5150        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5151    });
 5152    cx.assert_editor_state(indoc! {"
 5153        «aaaaa
 5154        bb
 5155        ccc
 5156        ddddˇ»
 5157    "});
 5158
 5159    // Manipulate with multiple disjoin selections
 5160    cx.set_state(indoc! {"
 5161 5162        4
 5163        3
 5164        2
 5165        1ˇ»
 5166
 5167        dd«dd
 5168        ccc
 5169        bb
 5170        aaaˇ»aa
 5171    "});
 5172    cx.update_editor(|e, window, cx| {
 5173        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5174    });
 5175    cx.assert_editor_state(indoc! {"
 5176        «1
 5177        2
 5178        3
 5179        4
 5180        5ˇ»
 5181
 5182        «aaaaa
 5183        bb
 5184        ccc
 5185        ddddˇ»
 5186    "});
 5187
 5188    // Adding lines on each selection
 5189    cx.set_state(indoc! {"
 5190 5191        1ˇ»
 5192
 5193        bb«bb
 5194        aaaˇ»aa
 5195    "});
 5196    cx.update_editor(|e, window, cx| {
 5197        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5198    });
 5199    cx.assert_editor_state(indoc! {"
 5200        «2
 5201        1
 5202        added lineˇ»
 5203
 5204        «bbbb
 5205        aaaaa
 5206        added lineˇ»
 5207    "});
 5208
 5209    // Removing lines on each selection
 5210    cx.set_state(indoc! {"
 5211 5212        1ˇ»
 5213
 5214        bb«bb
 5215        aaaˇ»aa
 5216    "});
 5217    cx.update_editor(|e, window, cx| {
 5218        e.manipulate_immutable_lines(window, cx, |lines| {
 5219            lines.pop();
 5220        })
 5221    });
 5222    cx.assert_editor_state(indoc! {"
 5223        «2ˇ»
 5224
 5225        «bbbbˇ»
 5226    "});
 5227}
 5228
 5229#[gpui::test]
 5230async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5231    init_test(cx, |settings| {
 5232        settings.defaults.tab_size = NonZeroU32::new(3)
 5233    });
 5234
 5235    let mut cx = EditorTestContext::new(cx).await;
 5236
 5237    // MULTI SELECTION
 5238    // Ln.1 "«" tests empty lines
 5239    // Ln.9 tests just leading whitespace
 5240    cx.set_state(indoc! {"
 5241        «
 5242        abc                 // No indentationˇ»
 5243        «\tabc              // 1 tabˇ»
 5244        \t\tabc «      ˇ»   // 2 tabs
 5245        \t ab«c             // Tab followed by space
 5246         \tabc              // Space followed by tab (3 spaces should be the result)
 5247        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5248           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5249        \t
 5250        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5251    "});
 5252    cx.update_editor(|e, window, cx| {
 5253        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5254    });
 5255    cx.assert_editor_state(
 5256        indoc! {"
 5257            «
 5258            abc                 // No indentation
 5259               abc              // 1 tab
 5260                  abc          // 2 tabs
 5261                abc             // Tab followed by space
 5262               abc              // Space followed by tab (3 spaces should be the result)
 5263                           abc   // Mixed indentation (tab conversion depends on the column)
 5264               abc         // Already space indented
 5265               ·
 5266               abc\tdef          // Only the leading tab is manipulatedˇ»
 5267        "}
 5268        .replace("·", "")
 5269        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5270    );
 5271
 5272    // Test on just a few lines, the others should remain unchanged
 5273    // Only lines (3, 5, 10, 11) should change
 5274    cx.set_state(
 5275        indoc! {"
 5276            ·
 5277            abc                 // No indentation
 5278            \tabcˇ               // 1 tab
 5279            \t\tabc             // 2 tabs
 5280            \t abcˇ              // Tab followed by space
 5281             \tabc              // Space followed by tab (3 spaces should be the result)
 5282            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5283               abc              // Already space indented
 5284            «\t
 5285            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5286        "}
 5287        .replace("·", "")
 5288        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5289    );
 5290    cx.update_editor(|e, window, cx| {
 5291        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5292    });
 5293    cx.assert_editor_state(
 5294        indoc! {"
 5295            ·
 5296            abc                 // No indentation
 5297            «   abc               // 1 tabˇ»
 5298            \t\tabc             // 2 tabs
 5299            «    abc              // Tab followed by spaceˇ»
 5300             \tabc              // Space followed by tab (3 spaces should be the result)
 5301            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5302               abc              // Already space indented
 5303            «   ·
 5304               abc\tdef          // Only the leading tab is manipulatedˇ»
 5305        "}
 5306        .replace("·", "")
 5307        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5308    );
 5309
 5310    // SINGLE SELECTION
 5311    // Ln.1 "«" tests empty lines
 5312    // Ln.9 tests just leading whitespace
 5313    cx.set_state(indoc! {"
 5314        «
 5315        abc                 // No indentation
 5316        \tabc               // 1 tab
 5317        \t\tabc             // 2 tabs
 5318        \t abc              // Tab followed by space
 5319         \tabc              // Space followed by tab (3 spaces should be the result)
 5320        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5321           abc              // Already space indented
 5322        \t
 5323        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5324    "});
 5325    cx.update_editor(|e, window, cx| {
 5326        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5327    });
 5328    cx.assert_editor_state(
 5329        indoc! {"
 5330            «
 5331            abc                 // No indentation
 5332               abc               // 1 tab
 5333                  abc             // 2 tabs
 5334                abc              // Tab followed by space
 5335               abc              // Space followed by tab (3 spaces should be the result)
 5336                           abc   // Mixed indentation (tab conversion depends on the column)
 5337               abc              // Already space indented
 5338               ·
 5339               abc\tdef          // Only the leading tab is manipulatedˇ»
 5340        "}
 5341        .replace("·", "")
 5342        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5343    );
 5344}
 5345
 5346#[gpui::test]
 5347async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5348    init_test(cx, |settings| {
 5349        settings.defaults.tab_size = NonZeroU32::new(3)
 5350    });
 5351
 5352    let mut cx = EditorTestContext::new(cx).await;
 5353
 5354    // MULTI SELECTION
 5355    // Ln.1 "«" tests empty lines
 5356    // Ln.11 tests just leading whitespace
 5357    cx.set_state(indoc! {"
 5358        «
 5359        abˇ»ˇc                 // No indentation
 5360         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5361          abc  «             // 2 spaces (< 3 so dont convert)
 5362           abc              // 3 spaces (convert)
 5363             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5364        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5365        «\t abc              // Tab followed by space
 5366         \tabc              // Space followed by tab (should be consumed due to tab)
 5367        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5368           \tˇ»  «\t
 5369           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5370    "});
 5371    cx.update_editor(|e, window, cx| {
 5372        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5373    });
 5374    cx.assert_editor_state(indoc! {"
 5375        «
 5376        abc                 // No indentation
 5377         abc                // 1 space (< 3 so dont convert)
 5378          abc               // 2 spaces (< 3 so dont convert)
 5379        \tabc              // 3 spaces (convert)
 5380        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5381        \t\t\tabc           // Already tab indented
 5382        \t abc              // Tab followed by space
 5383        \tabc              // Space followed by tab (should be consumed due to tab)
 5384        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5385        \t\t\t
 5386        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5387    "});
 5388
 5389    // Test on just a few lines, the other should remain unchanged
 5390    // Only lines (4, 8, 11, 12) should change
 5391    cx.set_state(
 5392        indoc! {"
 5393            ·
 5394            abc                 // No indentation
 5395             abc                // 1 space (< 3 so dont convert)
 5396              abc               // 2 spaces (< 3 so dont convert)
 5397            «   abc              // 3 spaces (convert)ˇ»
 5398                 abc            // 5 spaces (1 tab + 2 spaces)
 5399            \t\t\tabc           // Already tab indented
 5400            \t abc              // Tab followed by space
 5401             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5402               \t\t  \tabc      // Mixed indentation
 5403            \t \t  \t   \tabc   // Mixed indentation
 5404               \t  \tˇ
 5405            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5406        "}
 5407        .replace("·", "")
 5408        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5409    );
 5410    cx.update_editor(|e, window, cx| {
 5411        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5412    });
 5413    cx.assert_editor_state(
 5414        indoc! {"
 5415            ·
 5416            abc                 // No indentation
 5417             abc                // 1 space (< 3 so dont convert)
 5418              abc               // 2 spaces (< 3 so dont convert)
 5419            «\tabc              // 3 spaces (convert)ˇ»
 5420                 abc            // 5 spaces (1 tab + 2 spaces)
 5421            \t\t\tabc           // Already tab indented
 5422            \t abc              // Tab followed by space
 5423            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5424               \t\t  \tabc      // Mixed indentation
 5425            \t \t  \t   \tabc   // Mixed indentation
 5426            «\t\t\t
 5427            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5428        "}
 5429        .replace("·", "")
 5430        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5431    );
 5432
 5433    // SINGLE SELECTION
 5434    // Ln.1 "«" tests empty lines
 5435    // Ln.11 tests just leading whitespace
 5436    cx.set_state(indoc! {"
 5437        «
 5438        abc                 // No indentation
 5439         abc                // 1 space (< 3 so dont convert)
 5440          abc               // 2 spaces (< 3 so dont convert)
 5441           abc              // 3 spaces (convert)
 5442             abc            // 5 spaces (1 tab + 2 spaces)
 5443        \t\t\tabc           // Already tab indented
 5444        \t abc              // Tab followed by space
 5445         \tabc              // Space followed by tab (should be consumed due to tab)
 5446        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5447           \t  \t
 5448           abc   \t         // Only the leading spaces should be convertedˇ»
 5449    "});
 5450    cx.update_editor(|e, window, cx| {
 5451        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5452    });
 5453    cx.assert_editor_state(indoc! {"
 5454        «
 5455        abc                 // No indentation
 5456         abc                // 1 space (< 3 so dont convert)
 5457          abc               // 2 spaces (< 3 so dont convert)
 5458        \tabc              // 3 spaces (convert)
 5459        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5460        \t\t\tabc           // Already tab indented
 5461        \t abc              // Tab followed by space
 5462        \tabc              // Space followed by tab (should be consumed due to tab)
 5463        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5464        \t\t\t
 5465        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5466    "});
 5467}
 5468
 5469#[gpui::test]
 5470async fn test_toggle_case(cx: &mut TestAppContext) {
 5471    init_test(cx, |_| {});
 5472
 5473    let mut cx = EditorTestContext::new(cx).await;
 5474
 5475    // If all lower case -> upper case
 5476    cx.set_state(indoc! {"
 5477        «hello worldˇ»
 5478    "});
 5479    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5480    cx.assert_editor_state(indoc! {"
 5481        «HELLO WORLDˇ»
 5482    "});
 5483
 5484    // If all upper case -> lower case
 5485    cx.set_state(indoc! {"
 5486        «HELLO WORLDˇ»
 5487    "});
 5488    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5489    cx.assert_editor_state(indoc! {"
 5490        «hello worldˇ»
 5491    "});
 5492
 5493    // If any upper case characters are identified -> lower case
 5494    // This matches JetBrains IDEs
 5495    cx.set_state(indoc! {"
 5496        «hEllo worldˇ»
 5497    "});
 5498    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5499    cx.assert_editor_state(indoc! {"
 5500        «hello worldˇ»
 5501    "});
 5502}
 5503
 5504#[gpui::test]
 5505async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5506    init_test(cx, |_| {});
 5507
 5508    let mut cx = EditorTestContext::new(cx).await;
 5509
 5510    cx.set_state(indoc! {"
 5511        «implement-windows-supportˇ»
 5512    "});
 5513    cx.update_editor(|e, window, cx| {
 5514        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5515    });
 5516    cx.assert_editor_state(indoc! {"
 5517        «Implement windows supportˇ»
 5518    "});
 5519}
 5520
 5521#[gpui::test]
 5522async fn test_manipulate_text(cx: &mut TestAppContext) {
 5523    init_test(cx, |_| {});
 5524
 5525    let mut cx = EditorTestContext::new(cx).await;
 5526
 5527    // Test convert_to_upper_case()
 5528    cx.set_state(indoc! {"
 5529        «hello worldˇ»
 5530    "});
 5531    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5532    cx.assert_editor_state(indoc! {"
 5533        «HELLO WORLDˇ»
 5534    "});
 5535
 5536    // Test convert_to_lower_case()
 5537    cx.set_state(indoc! {"
 5538        «HELLO WORLDˇ»
 5539    "});
 5540    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5541    cx.assert_editor_state(indoc! {"
 5542        «hello worldˇ»
 5543    "});
 5544
 5545    // Test multiple line, single selection case
 5546    cx.set_state(indoc! {"
 5547        «The quick brown
 5548        fox jumps over
 5549        the lazy dogˇ»
 5550    "});
 5551    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5552    cx.assert_editor_state(indoc! {"
 5553        «The Quick Brown
 5554        Fox Jumps Over
 5555        The Lazy Dogˇ»
 5556    "});
 5557
 5558    // Test multiple line, single selection case
 5559    cx.set_state(indoc! {"
 5560        «The quick brown
 5561        fox jumps over
 5562        the lazy dogˇ»
 5563    "});
 5564    cx.update_editor(|e, window, cx| {
 5565        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5566    });
 5567    cx.assert_editor_state(indoc! {"
 5568        «TheQuickBrown
 5569        FoxJumpsOver
 5570        TheLazyDogˇ»
 5571    "});
 5572
 5573    // From here on out, test more complex cases of manipulate_text()
 5574
 5575    // Test no selection case - should affect words cursors are in
 5576    // Cursor at beginning, middle, and end of word
 5577    cx.set_state(indoc! {"
 5578        ˇhello big beauˇtiful worldˇ
 5579    "});
 5580    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5581    cx.assert_editor_state(indoc! {"
 5582        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5583    "});
 5584
 5585    // Test multiple selections on a single line and across multiple lines
 5586    cx.set_state(indoc! {"
 5587        «Theˇ» quick «brown
 5588        foxˇ» jumps «overˇ»
 5589        the «lazyˇ» dog
 5590    "});
 5591    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5592    cx.assert_editor_state(indoc! {"
 5593        «THEˇ» quick «BROWN
 5594        FOXˇ» jumps «OVERˇ»
 5595        the «LAZYˇ» dog
 5596    "});
 5597
 5598    // Test case where text length grows
 5599    cx.set_state(indoc! {"
 5600        «tschüߡ»
 5601    "});
 5602    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5603    cx.assert_editor_state(indoc! {"
 5604        «TSCHÜSSˇ»
 5605    "});
 5606
 5607    // Test to make sure we don't crash when text shrinks
 5608    cx.set_state(indoc! {"
 5609        aaa_bbbˇ
 5610    "});
 5611    cx.update_editor(|e, window, cx| {
 5612        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5613    });
 5614    cx.assert_editor_state(indoc! {"
 5615        «aaaBbbˇ»
 5616    "});
 5617
 5618    // Test to make sure we all aware of the fact that each word can grow and shrink
 5619    // Final selections should be aware of this fact
 5620    cx.set_state(indoc! {"
 5621        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5622    "});
 5623    cx.update_editor(|e, window, cx| {
 5624        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5625    });
 5626    cx.assert_editor_state(indoc! {"
 5627        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5628    "});
 5629
 5630    cx.set_state(indoc! {"
 5631        «hElLo, WoRld!ˇ»
 5632    "});
 5633    cx.update_editor(|e, window, cx| {
 5634        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5635    });
 5636    cx.assert_editor_state(indoc! {"
 5637        «HeLlO, wOrLD!ˇ»
 5638    "});
 5639
 5640    // Test selections with `line_mode() = true`.
 5641    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5642    cx.set_state(indoc! {"
 5643        «The quick brown
 5644        fox jumps over
 5645        tˇ»he lazy dog
 5646    "});
 5647    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5648    cx.assert_editor_state(indoc! {"
 5649        «THE QUICK BROWN
 5650        FOX JUMPS OVER
 5651        THE LAZY DOGˇ»
 5652    "});
 5653}
 5654
 5655#[gpui::test]
 5656fn test_duplicate_line(cx: &mut TestAppContext) {
 5657    init_test(cx, |_| {});
 5658
 5659    let editor = cx.add_window(|window, cx| {
 5660        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5661        build_editor(buffer, window, cx)
 5662    });
 5663    _ = editor.update(cx, |editor, window, cx| {
 5664        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5665            s.select_display_ranges([
 5666                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5667                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5668                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5669                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5670            ])
 5671        });
 5672        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5673        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5674        assert_eq!(
 5675            display_ranges(editor, cx),
 5676            vec![
 5677                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5678                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5679                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5680                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5681            ]
 5682        );
 5683    });
 5684
 5685    let editor = cx.add_window(|window, cx| {
 5686        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5687        build_editor(buffer, window, cx)
 5688    });
 5689    _ = editor.update(cx, |editor, window, cx| {
 5690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5691            s.select_display_ranges([
 5692                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5693                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5694            ])
 5695        });
 5696        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5697        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5698        assert_eq!(
 5699            display_ranges(editor, cx),
 5700            vec![
 5701                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5702                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5703            ]
 5704        );
 5705    });
 5706
 5707    // With `duplicate_line_up` the selections move to the duplicated lines,
 5708    // which are inserted above the original lines
 5709    let editor = cx.add_window(|window, cx| {
 5710        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5711        build_editor(buffer, window, cx)
 5712    });
 5713    _ = editor.update(cx, |editor, window, cx| {
 5714        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5715            s.select_display_ranges([
 5716                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5717                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5718                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5719                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5720            ])
 5721        });
 5722        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5723        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5724        assert_eq!(
 5725            display_ranges(editor, cx),
 5726            vec![
 5727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5728                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5729                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5730                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5731            ]
 5732        );
 5733    });
 5734
 5735    let editor = cx.add_window(|window, cx| {
 5736        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5737        build_editor(buffer, window, cx)
 5738    });
 5739    _ = editor.update(cx, |editor, window, cx| {
 5740        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5741            s.select_display_ranges([
 5742                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5743                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5744            ])
 5745        });
 5746        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5747        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5748        assert_eq!(
 5749            display_ranges(editor, cx),
 5750            vec![
 5751                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5752                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5753            ]
 5754        );
 5755    });
 5756
 5757    let editor = cx.add_window(|window, cx| {
 5758        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5759        build_editor(buffer, window, cx)
 5760    });
 5761    _ = editor.update(cx, |editor, window, cx| {
 5762        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5763            s.select_display_ranges([
 5764                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5765                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5766            ])
 5767        });
 5768        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5769        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5770        assert_eq!(
 5771            display_ranges(editor, cx),
 5772            vec![
 5773                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5774                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5775            ]
 5776        );
 5777    });
 5778}
 5779
 5780#[gpui::test]
 5781fn test_move_line_up_down(cx: &mut TestAppContext) {
 5782    init_test(cx, |_| {});
 5783
 5784    let editor = cx.add_window(|window, cx| {
 5785        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5786        build_editor(buffer, window, cx)
 5787    });
 5788    _ = editor.update(cx, |editor, window, cx| {
 5789        editor.fold_creases(
 5790            vec![
 5791                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5792                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5793                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5794            ],
 5795            true,
 5796            window,
 5797            cx,
 5798        );
 5799        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5800            s.select_display_ranges([
 5801                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5802                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5803                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5804                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5805            ])
 5806        });
 5807        assert_eq!(
 5808            editor.display_text(cx),
 5809            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5810        );
 5811
 5812        editor.move_line_up(&MoveLineUp, window, cx);
 5813        assert_eq!(
 5814            editor.display_text(cx),
 5815            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5816        );
 5817        assert_eq!(
 5818            display_ranges(editor, cx),
 5819            vec![
 5820                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5821                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5822                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5823                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5824            ]
 5825        );
 5826    });
 5827
 5828    _ = editor.update(cx, |editor, window, cx| {
 5829        editor.move_line_down(&MoveLineDown, window, cx);
 5830        assert_eq!(
 5831            editor.display_text(cx),
 5832            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5833        );
 5834        assert_eq!(
 5835            display_ranges(editor, cx),
 5836            vec![
 5837                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5838                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5839                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5840                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5841            ]
 5842        );
 5843    });
 5844
 5845    _ = editor.update(cx, |editor, window, cx| {
 5846        editor.move_line_down(&MoveLineDown, window, cx);
 5847        assert_eq!(
 5848            editor.display_text(cx),
 5849            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5850        );
 5851        assert_eq!(
 5852            display_ranges(editor, cx),
 5853            vec![
 5854                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5855                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5856                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5857                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5858            ]
 5859        );
 5860    });
 5861
 5862    _ = editor.update(cx, |editor, window, cx| {
 5863        editor.move_line_up(&MoveLineUp, window, cx);
 5864        assert_eq!(
 5865            editor.display_text(cx),
 5866            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5867        );
 5868        assert_eq!(
 5869            display_ranges(editor, cx),
 5870            vec![
 5871                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5872                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5873                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5874                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5875            ]
 5876        );
 5877    });
 5878}
 5879
 5880#[gpui::test]
 5881fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5882    init_test(cx, |_| {});
 5883    let editor = cx.add_window(|window, cx| {
 5884        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5885        build_editor(buffer, window, cx)
 5886    });
 5887    _ = editor.update(cx, |editor, window, cx| {
 5888        editor.fold_creases(
 5889            vec![Crease::simple(
 5890                Point::new(6, 4)..Point::new(7, 4),
 5891                FoldPlaceholder::test(),
 5892            )],
 5893            true,
 5894            window,
 5895            cx,
 5896        );
 5897        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5898            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5899        });
 5900        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5901        editor.move_line_up(&MoveLineUp, window, cx);
 5902        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5903        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5904    });
 5905}
 5906
 5907#[gpui::test]
 5908fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5909    init_test(cx, |_| {});
 5910
 5911    let editor = cx.add_window(|window, cx| {
 5912        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5913        build_editor(buffer, window, cx)
 5914    });
 5915    _ = editor.update(cx, |editor, window, cx| {
 5916        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5917        editor.insert_blocks(
 5918            [BlockProperties {
 5919                style: BlockStyle::Fixed,
 5920                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5921                height: Some(1),
 5922                render: Arc::new(|_| div().into_any()),
 5923                priority: 0,
 5924            }],
 5925            Some(Autoscroll::fit()),
 5926            cx,
 5927        );
 5928        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5929            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5930        });
 5931        editor.move_line_down(&MoveLineDown, window, cx);
 5932    });
 5933}
 5934
 5935#[gpui::test]
 5936async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5937    init_test(cx, |_| {});
 5938
 5939    let mut cx = EditorTestContext::new(cx).await;
 5940    cx.set_state(
 5941        &"
 5942            ˇzero
 5943            one
 5944            two
 5945            three
 5946            four
 5947            five
 5948        "
 5949        .unindent(),
 5950    );
 5951
 5952    // Create a four-line block that replaces three lines of text.
 5953    cx.update_editor(|editor, window, cx| {
 5954        let snapshot = editor.snapshot(window, cx);
 5955        let snapshot = &snapshot.buffer_snapshot();
 5956        let placement = BlockPlacement::Replace(
 5957            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5958        );
 5959        editor.insert_blocks(
 5960            [BlockProperties {
 5961                placement,
 5962                height: Some(4),
 5963                style: BlockStyle::Sticky,
 5964                render: Arc::new(|_| gpui::div().into_any_element()),
 5965                priority: 0,
 5966            }],
 5967            None,
 5968            cx,
 5969        );
 5970    });
 5971
 5972    // Move down so that the cursor touches the block.
 5973    cx.update_editor(|editor, window, cx| {
 5974        editor.move_down(&Default::default(), window, cx);
 5975    });
 5976    cx.assert_editor_state(
 5977        &"
 5978            zero
 5979            «one
 5980            two
 5981            threeˇ»
 5982            four
 5983            five
 5984        "
 5985        .unindent(),
 5986    );
 5987
 5988    // Move down past the block.
 5989    cx.update_editor(|editor, window, cx| {
 5990        editor.move_down(&Default::default(), window, cx);
 5991    });
 5992    cx.assert_editor_state(
 5993        &"
 5994            zero
 5995            one
 5996            two
 5997            three
 5998            ˇfour
 5999            five
 6000        "
 6001        .unindent(),
 6002    );
 6003}
 6004
 6005#[gpui::test]
 6006fn test_transpose(cx: &mut TestAppContext) {
 6007    init_test(cx, |_| {});
 6008
 6009    _ = cx.add_window(|window, cx| {
 6010        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6011        editor.set_style(EditorStyle::default(), window, cx);
 6012        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6013            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6014        });
 6015        editor.transpose(&Default::default(), window, cx);
 6016        assert_eq!(editor.text(cx), "bac");
 6017        assert_eq!(
 6018            editor.selections.ranges(&editor.display_snapshot(cx)),
 6019            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6020        );
 6021
 6022        editor.transpose(&Default::default(), window, cx);
 6023        assert_eq!(editor.text(cx), "bca");
 6024        assert_eq!(
 6025            editor.selections.ranges(&editor.display_snapshot(cx)),
 6026            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6027        );
 6028
 6029        editor.transpose(&Default::default(), window, cx);
 6030        assert_eq!(editor.text(cx), "bac");
 6031        assert_eq!(
 6032            editor.selections.ranges(&editor.display_snapshot(cx)),
 6033            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6034        );
 6035
 6036        editor
 6037    });
 6038
 6039    _ = cx.add_window(|window, cx| {
 6040        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6041        editor.set_style(EditorStyle::default(), window, cx);
 6042        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6043            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6044        });
 6045        editor.transpose(&Default::default(), window, cx);
 6046        assert_eq!(editor.text(cx), "acb\nde");
 6047        assert_eq!(
 6048            editor.selections.ranges(&editor.display_snapshot(cx)),
 6049            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6050        );
 6051
 6052        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6053            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6054        });
 6055        editor.transpose(&Default::default(), window, cx);
 6056        assert_eq!(editor.text(cx), "acbd\ne");
 6057        assert_eq!(
 6058            editor.selections.ranges(&editor.display_snapshot(cx)),
 6059            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6060        );
 6061
 6062        editor.transpose(&Default::default(), window, cx);
 6063        assert_eq!(editor.text(cx), "acbde\n");
 6064        assert_eq!(
 6065            editor.selections.ranges(&editor.display_snapshot(cx)),
 6066            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6067        );
 6068
 6069        editor.transpose(&Default::default(), window, cx);
 6070        assert_eq!(editor.text(cx), "acbd\ne");
 6071        assert_eq!(
 6072            editor.selections.ranges(&editor.display_snapshot(cx)),
 6073            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6074        );
 6075
 6076        editor
 6077    });
 6078
 6079    _ = cx.add_window(|window, cx| {
 6080        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6081        editor.set_style(EditorStyle::default(), window, cx);
 6082        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6083            s.select_ranges([
 6084                MultiBufferOffset(1)..MultiBufferOffset(1),
 6085                MultiBufferOffset(2)..MultiBufferOffset(2),
 6086                MultiBufferOffset(4)..MultiBufferOffset(4),
 6087            ])
 6088        });
 6089        editor.transpose(&Default::default(), window, cx);
 6090        assert_eq!(editor.text(cx), "bacd\ne");
 6091        assert_eq!(
 6092            editor.selections.ranges(&editor.display_snapshot(cx)),
 6093            [
 6094                MultiBufferOffset(2)..MultiBufferOffset(2),
 6095                MultiBufferOffset(3)..MultiBufferOffset(3),
 6096                MultiBufferOffset(5)..MultiBufferOffset(5)
 6097            ]
 6098        );
 6099
 6100        editor.transpose(&Default::default(), window, cx);
 6101        assert_eq!(editor.text(cx), "bcade\n");
 6102        assert_eq!(
 6103            editor.selections.ranges(&editor.display_snapshot(cx)),
 6104            [
 6105                MultiBufferOffset(3)..MultiBufferOffset(3),
 6106                MultiBufferOffset(4)..MultiBufferOffset(4),
 6107                MultiBufferOffset(6)..MultiBufferOffset(6)
 6108            ]
 6109        );
 6110
 6111        editor.transpose(&Default::default(), window, cx);
 6112        assert_eq!(editor.text(cx), "bcda\ne");
 6113        assert_eq!(
 6114            editor.selections.ranges(&editor.display_snapshot(cx)),
 6115            [
 6116                MultiBufferOffset(4)..MultiBufferOffset(4),
 6117                MultiBufferOffset(6)..MultiBufferOffset(6)
 6118            ]
 6119        );
 6120
 6121        editor.transpose(&Default::default(), window, cx);
 6122        assert_eq!(editor.text(cx), "bcade\n");
 6123        assert_eq!(
 6124            editor.selections.ranges(&editor.display_snapshot(cx)),
 6125            [
 6126                MultiBufferOffset(4)..MultiBufferOffset(4),
 6127                MultiBufferOffset(6)..MultiBufferOffset(6)
 6128            ]
 6129        );
 6130
 6131        editor.transpose(&Default::default(), window, cx);
 6132        assert_eq!(editor.text(cx), "bcaed\n");
 6133        assert_eq!(
 6134            editor.selections.ranges(&editor.display_snapshot(cx)),
 6135            [
 6136                MultiBufferOffset(5)..MultiBufferOffset(5),
 6137                MultiBufferOffset(6)..MultiBufferOffset(6)
 6138            ]
 6139        );
 6140
 6141        editor
 6142    });
 6143
 6144    _ = cx.add_window(|window, cx| {
 6145        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6146        editor.set_style(EditorStyle::default(), window, cx);
 6147        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6148            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6149        });
 6150        editor.transpose(&Default::default(), window, cx);
 6151        assert_eq!(editor.text(cx), "🏀🍐✋");
 6152        assert_eq!(
 6153            editor.selections.ranges(&editor.display_snapshot(cx)),
 6154            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6155        );
 6156
 6157        editor.transpose(&Default::default(), window, cx);
 6158        assert_eq!(editor.text(cx), "🏀✋🍐");
 6159        assert_eq!(
 6160            editor.selections.ranges(&editor.display_snapshot(cx)),
 6161            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6162        );
 6163
 6164        editor.transpose(&Default::default(), window, cx);
 6165        assert_eq!(editor.text(cx), "🏀🍐✋");
 6166        assert_eq!(
 6167            editor.selections.ranges(&editor.display_snapshot(cx)),
 6168            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6169        );
 6170
 6171        editor
 6172    });
 6173}
 6174
 6175#[gpui::test]
 6176async fn test_rewrap(cx: &mut TestAppContext) {
 6177    init_test(cx, |settings| {
 6178        settings.languages.0.extend([
 6179            (
 6180                "Markdown".into(),
 6181                LanguageSettingsContent {
 6182                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6183                    preferred_line_length: Some(40),
 6184                    ..Default::default()
 6185                },
 6186            ),
 6187            (
 6188                "Plain Text".into(),
 6189                LanguageSettingsContent {
 6190                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6191                    preferred_line_length: Some(40),
 6192                    ..Default::default()
 6193                },
 6194            ),
 6195            (
 6196                "C++".into(),
 6197                LanguageSettingsContent {
 6198                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6199                    preferred_line_length: Some(40),
 6200                    ..Default::default()
 6201                },
 6202            ),
 6203            (
 6204                "Python".into(),
 6205                LanguageSettingsContent {
 6206                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6207                    preferred_line_length: Some(40),
 6208                    ..Default::default()
 6209                },
 6210            ),
 6211            (
 6212                "Rust".into(),
 6213                LanguageSettingsContent {
 6214                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6215                    preferred_line_length: Some(40),
 6216                    ..Default::default()
 6217                },
 6218            ),
 6219        ])
 6220    });
 6221
 6222    let mut cx = EditorTestContext::new(cx).await;
 6223
 6224    let cpp_language = Arc::new(Language::new(
 6225        LanguageConfig {
 6226            name: "C++".into(),
 6227            line_comments: vec!["// ".into()],
 6228            ..LanguageConfig::default()
 6229        },
 6230        None,
 6231    ));
 6232    let python_language = Arc::new(Language::new(
 6233        LanguageConfig {
 6234            name: "Python".into(),
 6235            line_comments: vec!["# ".into()],
 6236            ..LanguageConfig::default()
 6237        },
 6238        None,
 6239    ));
 6240    let markdown_language = Arc::new(Language::new(
 6241        LanguageConfig {
 6242            name: "Markdown".into(),
 6243            rewrap_prefixes: vec![
 6244                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6245                regex::Regex::new("[-*+]\\s+").unwrap(),
 6246            ],
 6247            ..LanguageConfig::default()
 6248        },
 6249        None,
 6250    ));
 6251    let rust_language = Arc::new(
 6252        Language::new(
 6253            LanguageConfig {
 6254                name: "Rust".into(),
 6255                line_comments: vec!["// ".into(), "/// ".into()],
 6256                ..LanguageConfig::default()
 6257            },
 6258            Some(tree_sitter_rust::LANGUAGE.into()),
 6259        )
 6260        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6261        .unwrap(),
 6262    );
 6263
 6264    let plaintext_language = Arc::new(Language::new(
 6265        LanguageConfig {
 6266            name: "Plain Text".into(),
 6267            ..LanguageConfig::default()
 6268        },
 6269        None,
 6270    ));
 6271
 6272    // Test basic rewrapping of a long line with a cursor
 6273    assert_rewrap(
 6274        indoc! {"
 6275            // ˇThis is a long comment that needs to be wrapped.
 6276        "},
 6277        indoc! {"
 6278            // ˇThis is a long comment that needs to
 6279            // be wrapped.
 6280        "},
 6281        cpp_language.clone(),
 6282        &mut cx,
 6283    );
 6284
 6285    // Test rewrapping a full selection
 6286    assert_rewrap(
 6287        indoc! {"
 6288            «// This selected long comment needs to be wrapped.ˇ»"
 6289        },
 6290        indoc! {"
 6291            «// This selected long comment needs to
 6292            // be wrapped.ˇ»"
 6293        },
 6294        cpp_language.clone(),
 6295        &mut cx,
 6296    );
 6297
 6298    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6299    assert_rewrap(
 6300        indoc! {"
 6301            // ˇThis is the first line.
 6302            // Thisˇ is the second line.
 6303            // This is the thirdˇ line, all part of one paragraph.
 6304         "},
 6305        indoc! {"
 6306            // ˇThis is the first line. Thisˇ is the
 6307            // second line. This is the thirdˇ line,
 6308            // all part of one paragraph.
 6309         "},
 6310        cpp_language.clone(),
 6311        &mut cx,
 6312    );
 6313
 6314    // Test multiple cursors in different paragraphs trigger separate rewraps
 6315    assert_rewrap(
 6316        indoc! {"
 6317            // ˇThis is the first paragraph, first line.
 6318            // ˇThis is the first paragraph, second line.
 6319
 6320            // ˇThis is the second paragraph, first line.
 6321            // ˇThis is the second paragraph, second line.
 6322        "},
 6323        indoc! {"
 6324            // ˇThis is the first paragraph, first
 6325            // line. ˇThis is the first paragraph,
 6326            // second line.
 6327
 6328            // ˇThis is the second paragraph, first
 6329            // line. ˇThis is the second paragraph,
 6330            // second line.
 6331        "},
 6332        cpp_language.clone(),
 6333        &mut cx,
 6334    );
 6335
 6336    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6337    assert_rewrap(
 6338        indoc! {"
 6339            «// A regular long long comment to be wrapped.
 6340            /// A documentation long comment to be wrapped.ˇ»
 6341          "},
 6342        indoc! {"
 6343            «// A regular long long comment to be
 6344            // wrapped.
 6345            /// A documentation long comment to be
 6346            /// wrapped.ˇ»
 6347          "},
 6348        rust_language.clone(),
 6349        &mut cx,
 6350    );
 6351
 6352    // Test that change in indentation level trigger seperate rewraps
 6353    assert_rewrap(
 6354        indoc! {"
 6355            fn foo() {
 6356                «// This is a long comment at the base indent.
 6357                    // This is a long comment at the next indent.ˇ»
 6358            }
 6359        "},
 6360        indoc! {"
 6361            fn foo() {
 6362                «// This is a long comment at the
 6363                // base indent.
 6364                    // This is a long comment at the
 6365                    // next indent.ˇ»
 6366            }
 6367        "},
 6368        rust_language.clone(),
 6369        &mut cx,
 6370    );
 6371
 6372    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6373    assert_rewrap(
 6374        indoc! {"
 6375            # ˇThis is a long comment using a pound sign.
 6376        "},
 6377        indoc! {"
 6378            # ˇThis is a long comment using a pound
 6379            # sign.
 6380        "},
 6381        python_language,
 6382        &mut cx,
 6383    );
 6384
 6385    // Test rewrapping only affects comments, not code even when selected
 6386    assert_rewrap(
 6387        indoc! {"
 6388            «/// This doc comment is long and should be wrapped.
 6389            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6390        "},
 6391        indoc! {"
 6392            «/// This doc comment is long and should
 6393            /// be wrapped.
 6394            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6395        "},
 6396        rust_language.clone(),
 6397        &mut cx,
 6398    );
 6399
 6400    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6401    assert_rewrap(
 6402        indoc! {"
 6403            # Header
 6404
 6405            A long long long line of markdown text to wrap.ˇ
 6406         "},
 6407        indoc! {"
 6408            # Header
 6409
 6410            A long long long line of markdown text
 6411            to wrap.ˇ
 6412         "},
 6413        markdown_language.clone(),
 6414        &mut cx,
 6415    );
 6416
 6417    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6418    assert_rewrap(
 6419        indoc! {"
 6420            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6421            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6422            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6423        "},
 6424        indoc! {"
 6425            «1. This is a numbered list item that is
 6426               very long and needs to be wrapped
 6427               properly.
 6428            2. This is a numbered list item that is
 6429               very long and needs to be wrapped
 6430               properly.
 6431            - This is an unordered list item that is
 6432              also very long and should not merge
 6433              with the numbered item.ˇ»
 6434        "},
 6435        markdown_language.clone(),
 6436        &mut cx,
 6437    );
 6438
 6439    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6440    assert_rewrap(
 6441        indoc! {"
 6442            «1. This is a numbered list item that is
 6443            very long and needs to be wrapped
 6444            properly.
 6445            2. This is a numbered list item that is
 6446            very long and needs to be wrapped
 6447            properly.
 6448            - This is an unordered list item that is
 6449            also very long and should not merge with
 6450            the numbered item.ˇ»
 6451        "},
 6452        indoc! {"
 6453            «1. This is a numbered list item that is
 6454               very long and needs to be wrapped
 6455               properly.
 6456            2. This is a numbered list item that is
 6457               very long and needs to be wrapped
 6458               properly.
 6459            - This is an unordered list item that is
 6460              also very long and should not merge
 6461              with the numbered item.ˇ»
 6462        "},
 6463        markdown_language.clone(),
 6464        &mut cx,
 6465    );
 6466
 6467    // Test that rewrapping maintain indents even when they already exists.
 6468    assert_rewrap(
 6469        indoc! {"
 6470            «1. This is a numbered list
 6471               item that is very long and needs to be wrapped properly.
 6472            2. This is a numbered list
 6473               item that is very long and needs to be wrapped properly.
 6474            - This is an unordered list item that is also very long and
 6475              should not merge with the numbered item.ˇ»
 6476        "},
 6477        indoc! {"
 6478            «1. This is a numbered list item that is
 6479               very long and needs to be wrapped
 6480               properly.
 6481            2. This is a numbered list item that is
 6482               very long and needs to be wrapped
 6483               properly.
 6484            - This is an unordered list item that is
 6485              also very long and should not merge
 6486              with the numbered item.ˇ»
 6487        "},
 6488        markdown_language,
 6489        &mut cx,
 6490    );
 6491
 6492    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6493    assert_rewrap(
 6494        indoc! {"
 6495            ˇThis is a very long line of plain text that will be wrapped.
 6496        "},
 6497        indoc! {"
 6498            ˇThis is a very long line of plain text
 6499            that will be wrapped.
 6500        "},
 6501        plaintext_language.clone(),
 6502        &mut cx,
 6503    );
 6504
 6505    // Test that non-commented code acts as a paragraph boundary within a selection
 6506    assert_rewrap(
 6507        indoc! {"
 6508               «// This is the first long comment block to be wrapped.
 6509               fn my_func(a: u32);
 6510               // This is the second long comment block to be wrapped.ˇ»
 6511           "},
 6512        indoc! {"
 6513               «// This is the first long comment block
 6514               // to be wrapped.
 6515               fn my_func(a: u32);
 6516               // This is the second long comment block
 6517               // to be wrapped.ˇ»
 6518           "},
 6519        rust_language,
 6520        &mut cx,
 6521    );
 6522
 6523    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6524    assert_rewrap(
 6525        indoc! {"
 6526            «ˇThis is a very long line that will be wrapped.
 6527
 6528            This is another paragraph in the same selection.»
 6529
 6530            «\tThis is a very long indented line that will be wrapped.ˇ»
 6531         "},
 6532        indoc! {"
 6533            «ˇThis is a very long line that will be
 6534            wrapped.
 6535
 6536            This is another paragraph in the same
 6537            selection.»
 6538
 6539            «\tThis is a very long indented line
 6540            \tthat will be wrapped.ˇ»
 6541         "},
 6542        plaintext_language,
 6543        &mut cx,
 6544    );
 6545
 6546    // Test that an empty comment line acts as a paragraph boundary
 6547    assert_rewrap(
 6548        indoc! {"
 6549            // ˇThis is a long comment that will be wrapped.
 6550            //
 6551            // And this is another long comment that will also be wrapped.ˇ
 6552         "},
 6553        indoc! {"
 6554            // ˇThis is a long comment that will be
 6555            // wrapped.
 6556            //
 6557            // And this is another long comment that
 6558            // will also be wrapped.ˇ
 6559         "},
 6560        cpp_language,
 6561        &mut cx,
 6562    );
 6563
 6564    #[track_caller]
 6565    fn assert_rewrap(
 6566        unwrapped_text: &str,
 6567        wrapped_text: &str,
 6568        language: Arc<Language>,
 6569        cx: &mut EditorTestContext,
 6570    ) {
 6571        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6572        cx.set_state(unwrapped_text);
 6573        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6574        cx.assert_editor_state(wrapped_text);
 6575    }
 6576}
 6577
 6578#[gpui::test]
 6579async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6580    init_test(cx, |settings| {
 6581        settings.languages.0.extend([(
 6582            "Rust".into(),
 6583            LanguageSettingsContent {
 6584                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6585                preferred_line_length: Some(40),
 6586                ..Default::default()
 6587            },
 6588        )])
 6589    });
 6590
 6591    let mut cx = EditorTestContext::new(cx).await;
 6592
 6593    let rust_lang = Arc::new(
 6594        Language::new(
 6595            LanguageConfig {
 6596                name: "Rust".into(),
 6597                line_comments: vec!["// ".into()],
 6598                block_comment: Some(BlockCommentConfig {
 6599                    start: "/*".into(),
 6600                    end: "*/".into(),
 6601                    prefix: "* ".into(),
 6602                    tab_size: 1,
 6603                }),
 6604                documentation_comment: Some(BlockCommentConfig {
 6605                    start: "/**".into(),
 6606                    end: "*/".into(),
 6607                    prefix: "* ".into(),
 6608                    tab_size: 1,
 6609                }),
 6610
 6611                ..LanguageConfig::default()
 6612            },
 6613            Some(tree_sitter_rust::LANGUAGE.into()),
 6614        )
 6615        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6616        .unwrap(),
 6617    );
 6618
 6619    // regular block comment
 6620    assert_rewrap(
 6621        indoc! {"
 6622            /*
 6623             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6624             */
 6625            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6626        "},
 6627        indoc! {"
 6628            /*
 6629             *ˇ Lorem ipsum dolor sit amet,
 6630             * consectetur adipiscing elit.
 6631             */
 6632            /*
 6633             *ˇ Lorem ipsum dolor sit amet,
 6634             * consectetur adipiscing elit.
 6635             */
 6636        "},
 6637        rust_lang.clone(),
 6638        &mut cx,
 6639    );
 6640
 6641    // indent is respected
 6642    assert_rewrap(
 6643        indoc! {"
 6644            {}
 6645                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6646        "},
 6647        indoc! {"
 6648            {}
 6649                /*
 6650                 *ˇ Lorem ipsum dolor sit amet,
 6651                 * consectetur adipiscing elit.
 6652                 */
 6653        "},
 6654        rust_lang.clone(),
 6655        &mut cx,
 6656    );
 6657
 6658    // short block comments with inline delimiters
 6659    assert_rewrap(
 6660        indoc! {"
 6661            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6662            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6663             */
 6664            /*
 6665             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6666        "},
 6667        indoc! {"
 6668            /*
 6669             *ˇ Lorem ipsum dolor sit amet,
 6670             * consectetur adipiscing elit.
 6671             */
 6672            /*
 6673             *ˇ Lorem ipsum dolor sit amet,
 6674             * consectetur adipiscing elit.
 6675             */
 6676            /*
 6677             *ˇ Lorem ipsum dolor sit amet,
 6678             * consectetur adipiscing elit.
 6679             */
 6680        "},
 6681        rust_lang.clone(),
 6682        &mut cx,
 6683    );
 6684
 6685    // multiline block comment with inline start/end delimiters
 6686    assert_rewrap(
 6687        indoc! {"
 6688            /*ˇ Lorem ipsum dolor sit amet,
 6689             * consectetur adipiscing elit. */
 6690        "},
 6691        indoc! {"
 6692            /*
 6693             *ˇ Lorem ipsum dolor sit amet,
 6694             * consectetur adipiscing elit.
 6695             */
 6696        "},
 6697        rust_lang.clone(),
 6698        &mut cx,
 6699    );
 6700
 6701    // block comment rewrap still respects paragraph bounds
 6702    assert_rewrap(
 6703        indoc! {"
 6704            /*
 6705             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6706             *
 6707             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6708             */
 6709        "},
 6710        indoc! {"
 6711            /*
 6712             *ˇ Lorem ipsum dolor sit amet,
 6713             * consectetur adipiscing elit.
 6714             *
 6715             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6716             */
 6717        "},
 6718        rust_lang.clone(),
 6719        &mut cx,
 6720    );
 6721
 6722    // documentation comments
 6723    assert_rewrap(
 6724        indoc! {"
 6725            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6726            /**
 6727             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6728             */
 6729        "},
 6730        indoc! {"
 6731            /**
 6732             *ˇ Lorem ipsum dolor sit amet,
 6733             * consectetur adipiscing elit.
 6734             */
 6735            /**
 6736             *ˇ Lorem ipsum dolor sit amet,
 6737             * consectetur adipiscing elit.
 6738             */
 6739        "},
 6740        rust_lang.clone(),
 6741        &mut cx,
 6742    );
 6743
 6744    // different, adjacent comments
 6745    assert_rewrap(
 6746        indoc! {"
 6747            /**
 6748             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6749             */
 6750            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6751            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6752        "},
 6753        indoc! {"
 6754            /**
 6755             *ˇ Lorem ipsum dolor sit amet,
 6756             * consectetur adipiscing elit.
 6757             */
 6758            /*
 6759             *ˇ Lorem ipsum dolor sit amet,
 6760             * consectetur adipiscing elit.
 6761             */
 6762            //ˇ Lorem ipsum dolor sit amet,
 6763            // consectetur adipiscing elit.
 6764        "},
 6765        rust_lang.clone(),
 6766        &mut cx,
 6767    );
 6768
 6769    // selection w/ single short block comment
 6770    assert_rewrap(
 6771        indoc! {"
 6772            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6773        "},
 6774        indoc! {"
 6775            «/*
 6776             * Lorem ipsum dolor sit amet,
 6777             * consectetur adipiscing elit.
 6778             */ˇ»
 6779        "},
 6780        rust_lang.clone(),
 6781        &mut cx,
 6782    );
 6783
 6784    // rewrapping a single comment w/ abutting comments
 6785    assert_rewrap(
 6786        indoc! {"
 6787            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6788            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6789        "},
 6790        indoc! {"
 6791            /*
 6792             * ˇLorem ipsum dolor sit amet,
 6793             * consectetur adipiscing elit.
 6794             */
 6795            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6796        "},
 6797        rust_lang.clone(),
 6798        &mut cx,
 6799    );
 6800
 6801    // selection w/ non-abutting short block comments
 6802    assert_rewrap(
 6803        indoc! {"
 6804            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6805
 6806            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6807        "},
 6808        indoc! {"
 6809            «/*
 6810             * Lorem ipsum dolor sit amet,
 6811             * consectetur adipiscing elit.
 6812             */
 6813
 6814            /*
 6815             * Lorem ipsum dolor sit amet,
 6816             * consectetur adipiscing elit.
 6817             */ˇ»
 6818        "},
 6819        rust_lang.clone(),
 6820        &mut cx,
 6821    );
 6822
 6823    // selection of multiline block comments
 6824    assert_rewrap(
 6825        indoc! {"
 6826            «/* Lorem ipsum dolor sit amet,
 6827             * consectetur adipiscing elit. */ˇ»
 6828        "},
 6829        indoc! {"
 6830            «/*
 6831             * Lorem ipsum dolor sit amet,
 6832             * consectetur adipiscing elit.
 6833             */ˇ»
 6834        "},
 6835        rust_lang.clone(),
 6836        &mut cx,
 6837    );
 6838
 6839    // partial selection of multiline block comments
 6840    assert_rewrap(
 6841        indoc! {"
 6842            «/* Lorem ipsum dolor sit amet,ˇ»
 6843             * consectetur adipiscing elit. */
 6844            /* Lorem ipsum dolor sit amet,
 6845             «* consectetur adipiscing elit. */ˇ»
 6846        "},
 6847        indoc! {"
 6848            «/*
 6849             * Lorem ipsum dolor sit amet,ˇ»
 6850             * consectetur adipiscing elit. */
 6851            /* Lorem ipsum dolor sit amet,
 6852             «* consectetur adipiscing elit.
 6853             */ˇ»
 6854        "},
 6855        rust_lang.clone(),
 6856        &mut cx,
 6857    );
 6858
 6859    // selection w/ abutting short block comments
 6860    // TODO: should not be combined; should rewrap as 2 comments
 6861    assert_rewrap(
 6862        indoc! {"
 6863            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6864            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6865        "},
 6866        // desired behavior:
 6867        // indoc! {"
 6868        //     «/*
 6869        //      * Lorem ipsum dolor sit amet,
 6870        //      * consectetur adipiscing elit.
 6871        //      */
 6872        //     /*
 6873        //      * Lorem ipsum dolor sit amet,
 6874        //      * consectetur adipiscing elit.
 6875        //      */ˇ»
 6876        // "},
 6877        // actual behaviour:
 6878        indoc! {"
 6879            «/*
 6880             * Lorem ipsum dolor sit amet,
 6881             * consectetur adipiscing elit. Lorem
 6882             * ipsum dolor sit amet, consectetur
 6883             * adipiscing elit.
 6884             */ˇ»
 6885        "},
 6886        rust_lang.clone(),
 6887        &mut cx,
 6888    );
 6889
 6890    // TODO: same as above, but with delimiters on separate line
 6891    // assert_rewrap(
 6892    //     indoc! {"
 6893    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6894    //          */
 6895    //         /*
 6896    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6897    //     "},
 6898    //     // desired:
 6899    //     // indoc! {"
 6900    //     //     «/*
 6901    //     //      * Lorem ipsum dolor sit amet,
 6902    //     //      * consectetur adipiscing elit.
 6903    //     //      */
 6904    //     //     /*
 6905    //     //      * Lorem ipsum dolor sit amet,
 6906    //     //      * consectetur adipiscing elit.
 6907    //     //      */ˇ»
 6908    //     // "},
 6909    //     // actual: (but with trailing w/s on the empty lines)
 6910    //     indoc! {"
 6911    //         «/*
 6912    //          * Lorem ipsum dolor sit amet,
 6913    //          * consectetur adipiscing elit.
 6914    //          *
 6915    //          */
 6916    //         /*
 6917    //          *
 6918    //          * Lorem ipsum dolor sit amet,
 6919    //          * consectetur adipiscing elit.
 6920    //          */ˇ»
 6921    //     "},
 6922    //     rust_lang.clone(),
 6923    //     &mut cx,
 6924    // );
 6925
 6926    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6927    assert_rewrap(
 6928        indoc! {"
 6929            /*
 6930             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6931             */
 6932            /*
 6933             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6934            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6935        "},
 6936        // desired:
 6937        // indoc! {"
 6938        //     /*
 6939        //      *ˇ Lorem ipsum dolor sit amet,
 6940        //      * consectetur adipiscing elit.
 6941        //      */
 6942        //     /*
 6943        //      *ˇ Lorem ipsum dolor sit amet,
 6944        //      * consectetur adipiscing elit.
 6945        //      */
 6946        //     /*
 6947        //      *ˇ Lorem ipsum dolor sit amet
 6948        //      */ /* consectetur adipiscing elit. */
 6949        // "},
 6950        // actual:
 6951        indoc! {"
 6952            /*
 6953             //ˇ Lorem ipsum dolor sit amet,
 6954             // consectetur adipiscing elit.
 6955             */
 6956            /*
 6957             * //ˇ Lorem ipsum dolor sit amet,
 6958             * consectetur adipiscing elit.
 6959             */
 6960            /*
 6961             *ˇ Lorem ipsum dolor sit amet */ /*
 6962             * consectetur adipiscing elit.
 6963             */
 6964        "},
 6965        rust_lang,
 6966        &mut cx,
 6967    );
 6968
 6969    #[track_caller]
 6970    fn assert_rewrap(
 6971        unwrapped_text: &str,
 6972        wrapped_text: &str,
 6973        language: Arc<Language>,
 6974        cx: &mut EditorTestContext,
 6975    ) {
 6976        cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
 6977        cx.set_state(unwrapped_text);
 6978        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6979        cx.assert_editor_state(wrapped_text);
 6980    }
 6981}
 6982
 6983#[gpui::test]
 6984async fn test_hard_wrap(cx: &mut TestAppContext) {
 6985    init_test(cx, |_| {});
 6986    let mut cx = EditorTestContext::new(cx).await;
 6987
 6988    cx.update_buffer(|buffer, cx| buffer.set_language(Some(git_commit_lang()), cx));
 6989    cx.update_editor(|editor, _, cx| {
 6990        editor.set_hard_wrap(Some(14), cx);
 6991    });
 6992
 6993    cx.set_state(indoc!(
 6994        "
 6995        one two three ˇ
 6996        "
 6997    ));
 6998    cx.simulate_input("four");
 6999    cx.run_until_parked();
 7000
 7001    cx.assert_editor_state(indoc!(
 7002        "
 7003        one two three
 7004        fourˇ
 7005        "
 7006    ));
 7007
 7008    cx.update_editor(|editor, window, cx| {
 7009        editor.newline(&Default::default(), window, cx);
 7010    });
 7011    cx.run_until_parked();
 7012    cx.assert_editor_state(indoc!(
 7013        "
 7014        one two three
 7015        four
 7016        ˇ
 7017        "
 7018    ));
 7019
 7020    cx.simulate_input("five");
 7021    cx.run_until_parked();
 7022    cx.assert_editor_state(indoc!(
 7023        "
 7024        one two three
 7025        four
 7026        fiveˇ
 7027        "
 7028    ));
 7029
 7030    cx.update_editor(|editor, window, cx| {
 7031        editor.newline(&Default::default(), window, cx);
 7032    });
 7033    cx.run_until_parked();
 7034    cx.simulate_input("# ");
 7035    cx.run_until_parked();
 7036    cx.assert_editor_state(indoc!(
 7037        "
 7038        one two three
 7039        four
 7040        five
 7041        # ˇ
 7042        "
 7043    ));
 7044
 7045    cx.update_editor(|editor, window, cx| {
 7046        editor.newline(&Default::default(), window, cx);
 7047    });
 7048    cx.run_until_parked();
 7049    cx.assert_editor_state(indoc!(
 7050        "
 7051        one two three
 7052        four
 7053        five
 7054        #\x20
 7055 7056        "
 7057    ));
 7058
 7059    cx.simulate_input(" 6");
 7060    cx.run_until_parked();
 7061    cx.assert_editor_state(indoc!(
 7062        "
 7063        one two three
 7064        four
 7065        five
 7066        #
 7067        # 6ˇ
 7068        "
 7069    ));
 7070}
 7071
 7072#[gpui::test]
 7073async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7074    init_test(cx, |_| {});
 7075
 7076    let mut cx = EditorTestContext::new(cx).await;
 7077
 7078    cx.set_state(indoc! {"The quick brownˇ"});
 7079    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7080    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7081
 7082    cx.set_state(indoc! {"The emacs foxˇ"});
 7083    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7084    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7085
 7086    cx.set_state(indoc! {"
 7087        The quick« brownˇ»
 7088        fox jumps overˇ
 7089        the lazy dog"});
 7090    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7091    cx.assert_editor_state(indoc! {"
 7092        The quickˇ
 7093        ˇthe lazy dog"});
 7094
 7095    cx.set_state(indoc! {"
 7096        The quick« brownˇ»
 7097        fox jumps overˇ
 7098        the lazy dog"});
 7099    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7100    cx.assert_editor_state(indoc! {"
 7101        The quickˇ
 7102        fox jumps overˇthe lazy dog"});
 7103
 7104    cx.set_state(indoc! {"
 7105        The quick« brownˇ»
 7106        fox jumps overˇ
 7107        the lazy dog"});
 7108    cx.update_editor(|e, window, cx| {
 7109        e.cut_to_end_of_line(
 7110            &CutToEndOfLine {
 7111                stop_at_newlines: true,
 7112            },
 7113            window,
 7114            cx,
 7115        )
 7116    });
 7117    cx.assert_editor_state(indoc! {"
 7118        The quickˇ
 7119        fox jumps overˇ
 7120        the lazy dog"});
 7121
 7122    cx.set_state(indoc! {"
 7123        The quick« brownˇ»
 7124        fox jumps overˇ
 7125        the lazy dog"});
 7126    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7127    cx.assert_editor_state(indoc! {"
 7128        The quickˇ
 7129        fox jumps overˇthe lazy dog"});
 7130}
 7131
 7132#[gpui::test]
 7133async fn test_clipboard(cx: &mut TestAppContext) {
 7134    init_test(cx, |_| {});
 7135
 7136    let mut cx = EditorTestContext::new(cx).await;
 7137
 7138    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7139    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7140    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7141
 7142    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7143    cx.set_state("two ˇfour ˇsix ˇ");
 7144    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7145    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7146
 7147    // Paste again but with only two cursors. Since the number of cursors doesn't
 7148    // match the number of slices in the clipboard, the entire clipboard text
 7149    // is pasted at each cursor.
 7150    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7151    cx.update_editor(|e, window, cx| {
 7152        e.handle_input("( ", window, cx);
 7153        e.paste(&Paste, window, cx);
 7154        e.handle_input(") ", window, cx);
 7155    });
 7156    cx.assert_editor_state(
 7157        &([
 7158            "( one✅ ",
 7159            "three ",
 7160            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7161            "three ",
 7162            "five ) ˇ",
 7163        ]
 7164        .join("\n")),
 7165    );
 7166
 7167    // Cut with three selections, one of which is full-line.
 7168    cx.set_state(indoc! {"
 7169        1«2ˇ»3
 7170        4ˇ567
 7171        «8ˇ»9"});
 7172    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7173    cx.assert_editor_state(indoc! {"
 7174        1ˇ3
 7175        ˇ9"});
 7176
 7177    // Paste with three selections, noticing how the copied selection that was full-line
 7178    // gets inserted before the second cursor.
 7179    cx.set_state(indoc! {"
 7180        1ˇ3
 7181 7182        «oˇ»ne"});
 7183    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7184    cx.assert_editor_state(indoc! {"
 7185        12ˇ3
 7186        4567
 7187 7188        8ˇne"});
 7189
 7190    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7191    cx.set_state(indoc! {"
 7192        The quick brown
 7193        fox juˇmps over
 7194        the lazy dog"});
 7195    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7196    assert_eq!(
 7197        cx.read_from_clipboard()
 7198            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7199        Some("fox jumps over\n".to_string())
 7200    );
 7201
 7202    // Paste with three selections, noticing how the copied full-line selection is inserted
 7203    // before the empty selections but replaces the selection that is non-empty.
 7204    cx.set_state(indoc! {"
 7205        Tˇhe quick brown
 7206        «foˇ»x jumps over
 7207        tˇhe lazy dog"});
 7208    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7209    cx.assert_editor_state(indoc! {"
 7210        fox jumps over
 7211        Tˇhe quick brown
 7212        fox jumps over
 7213        ˇx jumps over
 7214        fox jumps over
 7215        tˇhe lazy dog"});
 7216}
 7217
 7218#[gpui::test]
 7219async fn test_copy_trim(cx: &mut TestAppContext) {
 7220    init_test(cx, |_| {});
 7221
 7222    let mut cx = EditorTestContext::new(cx).await;
 7223    cx.set_state(
 7224        r#"            «for selection in selections.iter() {
 7225            let mut start = selection.start;
 7226            let mut end = selection.end;
 7227            let is_entire_line = selection.is_empty();
 7228            if is_entire_line {
 7229                start = Point::new(start.row, 0);ˇ»
 7230                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7231            }
 7232        "#,
 7233    );
 7234    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7235    assert_eq!(
 7236        cx.read_from_clipboard()
 7237            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7238        Some(
 7239            "for selection in selections.iter() {
 7240            let mut start = selection.start;
 7241            let mut end = selection.end;
 7242            let is_entire_line = selection.is_empty();
 7243            if is_entire_line {
 7244                start = Point::new(start.row, 0);"
 7245                .to_string()
 7246        ),
 7247        "Regular copying preserves all indentation selected",
 7248    );
 7249    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7250    assert_eq!(
 7251        cx.read_from_clipboard()
 7252            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7253        Some(
 7254            "for selection in selections.iter() {
 7255let mut start = selection.start;
 7256let mut end = selection.end;
 7257let is_entire_line = selection.is_empty();
 7258if is_entire_line {
 7259    start = Point::new(start.row, 0);"
 7260                .to_string()
 7261        ),
 7262        "Copying with stripping should strip all leading whitespaces"
 7263    );
 7264
 7265    cx.set_state(
 7266        r#"       «     for selection in selections.iter() {
 7267            let mut start = selection.start;
 7268            let mut end = selection.end;
 7269            let is_entire_line = selection.is_empty();
 7270            if is_entire_line {
 7271                start = Point::new(start.row, 0);ˇ»
 7272                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7273            }
 7274        "#,
 7275    );
 7276    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7277    assert_eq!(
 7278        cx.read_from_clipboard()
 7279            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7280        Some(
 7281            "     for selection in selections.iter() {
 7282            let mut start = selection.start;
 7283            let mut end = selection.end;
 7284            let is_entire_line = selection.is_empty();
 7285            if is_entire_line {
 7286                start = Point::new(start.row, 0);"
 7287                .to_string()
 7288        ),
 7289        "Regular copying preserves all indentation selected",
 7290    );
 7291    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7292    assert_eq!(
 7293        cx.read_from_clipboard()
 7294            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7295        Some(
 7296            "for selection in selections.iter() {
 7297let mut start = selection.start;
 7298let mut end = selection.end;
 7299let is_entire_line = selection.is_empty();
 7300if is_entire_line {
 7301    start = Point::new(start.row, 0);"
 7302                .to_string()
 7303        ),
 7304        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7305    );
 7306
 7307    cx.set_state(
 7308        r#"       «ˇ     for selection in selections.iter() {
 7309            let mut start = selection.start;
 7310            let mut end = selection.end;
 7311            let is_entire_line = selection.is_empty();
 7312            if is_entire_line {
 7313                start = Point::new(start.row, 0);»
 7314                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7315            }
 7316        "#,
 7317    );
 7318    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7319    assert_eq!(
 7320        cx.read_from_clipboard()
 7321            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7322        Some(
 7323            "     for selection in selections.iter() {
 7324            let mut start = selection.start;
 7325            let mut end = selection.end;
 7326            let is_entire_line = selection.is_empty();
 7327            if is_entire_line {
 7328                start = Point::new(start.row, 0);"
 7329                .to_string()
 7330        ),
 7331        "Regular copying for reverse selection works the same",
 7332    );
 7333    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7334    assert_eq!(
 7335        cx.read_from_clipboard()
 7336            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7337        Some(
 7338            "for selection in selections.iter() {
 7339let mut start = selection.start;
 7340let mut end = selection.end;
 7341let is_entire_line = selection.is_empty();
 7342if is_entire_line {
 7343    start = Point::new(start.row, 0);"
 7344                .to_string()
 7345        ),
 7346        "Copying with stripping for reverse selection works the same"
 7347    );
 7348
 7349    cx.set_state(
 7350        r#"            for selection «in selections.iter() {
 7351            let mut start = selection.start;
 7352            let mut end = selection.end;
 7353            let is_entire_line = selection.is_empty();
 7354            if is_entire_line {
 7355                start = Point::new(start.row, 0);ˇ»
 7356                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7357            }
 7358        "#,
 7359    );
 7360    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7361    assert_eq!(
 7362        cx.read_from_clipboard()
 7363            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7364        Some(
 7365            "in selections.iter() {
 7366            let mut start = selection.start;
 7367            let mut end = selection.end;
 7368            let is_entire_line = selection.is_empty();
 7369            if is_entire_line {
 7370                start = Point::new(start.row, 0);"
 7371                .to_string()
 7372        ),
 7373        "When selecting past the indent, the copying works as usual",
 7374    );
 7375    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7376    assert_eq!(
 7377        cx.read_from_clipboard()
 7378            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7379        Some(
 7380            "in selections.iter() {
 7381            let mut start = selection.start;
 7382            let mut end = selection.end;
 7383            let is_entire_line = selection.is_empty();
 7384            if is_entire_line {
 7385                start = Point::new(start.row, 0);"
 7386                .to_string()
 7387        ),
 7388        "When selecting past the indent, nothing is trimmed"
 7389    );
 7390
 7391    cx.set_state(
 7392        r#"            «for selection in selections.iter() {
 7393            let mut start = selection.start;
 7394
 7395            let mut end = selection.end;
 7396            let is_entire_line = selection.is_empty();
 7397            if is_entire_line {
 7398                start = Point::new(start.row, 0);
 7399ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7400            }
 7401        "#,
 7402    );
 7403    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7404    assert_eq!(
 7405        cx.read_from_clipboard()
 7406            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7407        Some(
 7408            "for selection in selections.iter() {
 7409let mut start = selection.start;
 7410
 7411let mut end = selection.end;
 7412let is_entire_line = selection.is_empty();
 7413if is_entire_line {
 7414    start = Point::new(start.row, 0);
 7415"
 7416            .to_string()
 7417        ),
 7418        "Copying with stripping should ignore empty lines"
 7419    );
 7420}
 7421
 7422#[gpui::test]
 7423async fn test_paste_multiline(cx: &mut TestAppContext) {
 7424    init_test(cx, |_| {});
 7425
 7426    let mut cx = EditorTestContext::new(cx).await;
 7427    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7428
 7429    // Cut an indented block, without the leading whitespace.
 7430    cx.set_state(indoc! {"
 7431        const a: B = (
 7432            c(),
 7433            «d(
 7434                e,
 7435                f
 7436            )ˇ»
 7437        );
 7438    "});
 7439    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7440    cx.assert_editor_state(indoc! {"
 7441        const a: B = (
 7442            c(),
 7443            ˇ
 7444        );
 7445    "});
 7446
 7447    // Paste it at the same position.
 7448    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7449    cx.assert_editor_state(indoc! {"
 7450        const a: B = (
 7451            c(),
 7452            d(
 7453                e,
 7454                f
 7455 7456        );
 7457    "});
 7458
 7459    // Paste it at a line with a lower indent level.
 7460    cx.set_state(indoc! {"
 7461        ˇ
 7462        const a: B = (
 7463            c(),
 7464        );
 7465    "});
 7466    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7467    cx.assert_editor_state(indoc! {"
 7468        d(
 7469            e,
 7470            f
 7471 7472        const a: B = (
 7473            c(),
 7474        );
 7475    "});
 7476
 7477    // Cut an indented block, with the leading whitespace.
 7478    cx.set_state(indoc! {"
 7479        const a: B = (
 7480            c(),
 7481        «    d(
 7482                e,
 7483                f
 7484            )
 7485        ˇ»);
 7486    "});
 7487    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7488    cx.assert_editor_state(indoc! {"
 7489        const a: B = (
 7490            c(),
 7491        ˇ);
 7492    "});
 7493
 7494    // Paste it at the same position.
 7495    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7496    cx.assert_editor_state(indoc! {"
 7497        const a: B = (
 7498            c(),
 7499            d(
 7500                e,
 7501                f
 7502            )
 7503        ˇ);
 7504    "});
 7505
 7506    // Paste it at a line with a higher indent level.
 7507    cx.set_state(indoc! {"
 7508        const a: B = (
 7509            c(),
 7510            d(
 7511                e,
 7512 7513            )
 7514        );
 7515    "});
 7516    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7517    cx.assert_editor_state(indoc! {"
 7518        const a: B = (
 7519            c(),
 7520            d(
 7521                e,
 7522                f    d(
 7523                    e,
 7524                    f
 7525                )
 7526        ˇ
 7527            )
 7528        );
 7529    "});
 7530
 7531    // Copy an indented block, starting mid-line
 7532    cx.set_state(indoc! {"
 7533        const a: B = (
 7534            c(),
 7535            somethin«g(
 7536                e,
 7537                f
 7538            )ˇ»
 7539        );
 7540    "});
 7541    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7542
 7543    // Paste it on a line with a lower indent level
 7544    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7545    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7546    cx.assert_editor_state(indoc! {"
 7547        const a: B = (
 7548            c(),
 7549            something(
 7550                e,
 7551                f
 7552            )
 7553        );
 7554        g(
 7555            e,
 7556            f
 7557"});
 7558}
 7559
 7560#[gpui::test]
 7561async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7562    init_test(cx, |_| {});
 7563
 7564    cx.write_to_clipboard(ClipboardItem::new_string(
 7565        "    d(\n        e\n    );\n".into(),
 7566    ));
 7567
 7568    let mut cx = EditorTestContext::new(cx).await;
 7569    cx.update_buffer(|buffer, cx| buffer.set_language(Some(rust_lang()), cx));
 7570
 7571    cx.set_state(indoc! {"
 7572        fn a() {
 7573            b();
 7574            if c() {
 7575                ˇ
 7576            }
 7577        }
 7578    "});
 7579
 7580    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7581    cx.assert_editor_state(indoc! {"
 7582        fn a() {
 7583            b();
 7584            if c() {
 7585                d(
 7586                    e
 7587                );
 7588        ˇ
 7589            }
 7590        }
 7591    "});
 7592
 7593    cx.set_state(indoc! {"
 7594        fn a() {
 7595            b();
 7596            ˇ
 7597        }
 7598    "});
 7599
 7600    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7601    cx.assert_editor_state(indoc! {"
 7602        fn a() {
 7603            b();
 7604            d(
 7605                e
 7606            );
 7607        ˇ
 7608        }
 7609    "});
 7610}
 7611
 7612#[gpui::test]
 7613fn test_select_all(cx: &mut TestAppContext) {
 7614    init_test(cx, |_| {});
 7615
 7616    let editor = cx.add_window(|window, cx| {
 7617        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7618        build_editor(buffer, window, cx)
 7619    });
 7620    _ = editor.update(cx, |editor, window, cx| {
 7621        editor.select_all(&SelectAll, window, cx);
 7622        assert_eq!(
 7623            display_ranges(editor, cx),
 7624            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7625        );
 7626    });
 7627}
 7628
 7629#[gpui::test]
 7630fn test_select_line(cx: &mut TestAppContext) {
 7631    init_test(cx, |_| {});
 7632
 7633    let editor = cx.add_window(|window, cx| {
 7634        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7635        build_editor(buffer, window, cx)
 7636    });
 7637    _ = editor.update(cx, |editor, window, cx| {
 7638        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7639            s.select_display_ranges([
 7640                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7641                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7642                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7643                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7644            ])
 7645        });
 7646        editor.select_line(&SelectLine, window, cx);
 7647        assert_eq!(
 7648            display_ranges(editor, cx),
 7649            vec![
 7650                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7651                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7652            ]
 7653        );
 7654    });
 7655
 7656    _ = editor.update(cx, |editor, window, cx| {
 7657        editor.select_line(&SelectLine, window, cx);
 7658        assert_eq!(
 7659            display_ranges(editor, cx),
 7660            vec![
 7661                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7662                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7663            ]
 7664        );
 7665    });
 7666
 7667    _ = editor.update(cx, |editor, window, cx| {
 7668        editor.select_line(&SelectLine, window, cx);
 7669        assert_eq!(
 7670            display_ranges(editor, cx),
 7671            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7672        );
 7673    });
 7674}
 7675
 7676#[gpui::test]
 7677async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7678    init_test(cx, |_| {});
 7679    let mut cx = EditorTestContext::new(cx).await;
 7680
 7681    #[track_caller]
 7682    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7683        cx.set_state(initial_state);
 7684        cx.update_editor(|e, window, cx| {
 7685            e.split_selection_into_lines(&Default::default(), window, cx)
 7686        });
 7687        cx.assert_editor_state(expected_state);
 7688    }
 7689
 7690    // Selection starts and ends at the middle of lines, left-to-right
 7691    test(
 7692        &mut cx,
 7693        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7694        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7695    );
 7696    // Same thing, right-to-left
 7697    test(
 7698        &mut cx,
 7699        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7700        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7701    );
 7702
 7703    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7704    test(
 7705        &mut cx,
 7706        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7707        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7708    );
 7709    // Same thing, right-to-left
 7710    test(
 7711        &mut cx,
 7712        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7713        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7714    );
 7715
 7716    // Whole buffer, left-to-right, last line ends with newline
 7717    test(
 7718        &mut cx,
 7719        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7720        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7721    );
 7722    // Same thing, right-to-left
 7723    test(
 7724        &mut cx,
 7725        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7726        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7727    );
 7728
 7729    // Starts at the end of a line, ends at the start of another
 7730    test(
 7731        &mut cx,
 7732        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7733        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7734    );
 7735}
 7736
 7737#[gpui::test]
 7738async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7739    init_test(cx, |_| {});
 7740
 7741    let editor = cx.add_window(|window, cx| {
 7742        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7743        build_editor(buffer, window, cx)
 7744    });
 7745
 7746    // setup
 7747    _ = editor.update(cx, |editor, window, cx| {
 7748        editor.fold_creases(
 7749            vec![
 7750                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7751                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7752                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7753            ],
 7754            true,
 7755            window,
 7756            cx,
 7757        );
 7758        assert_eq!(
 7759            editor.display_text(cx),
 7760            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7761        );
 7762    });
 7763
 7764    _ = editor.update(cx, |editor, window, cx| {
 7765        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7766            s.select_display_ranges([
 7767                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7768                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7769                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7770                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7771            ])
 7772        });
 7773        editor.split_selection_into_lines(&Default::default(), window, cx);
 7774        assert_eq!(
 7775            editor.display_text(cx),
 7776            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7777        );
 7778    });
 7779    EditorTestContext::for_editor(editor, cx)
 7780        .await
 7781        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7782
 7783    _ = editor.update(cx, |editor, window, cx| {
 7784        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7785            s.select_display_ranges([
 7786                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7787            ])
 7788        });
 7789        editor.split_selection_into_lines(&Default::default(), window, cx);
 7790        assert_eq!(
 7791            editor.display_text(cx),
 7792            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7793        );
 7794        assert_eq!(
 7795            display_ranges(editor, cx),
 7796            [
 7797                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7798                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7799                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7800                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7801                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7802                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7803                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7804            ]
 7805        );
 7806    });
 7807    EditorTestContext::for_editor(editor, cx)
 7808        .await
 7809        .assert_editor_state(
 7810            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7811        );
 7812}
 7813
 7814#[gpui::test]
 7815async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7816    init_test(cx, |_| {});
 7817
 7818    let mut cx = EditorTestContext::new(cx).await;
 7819
 7820    cx.set_state(indoc!(
 7821        r#"abc
 7822           defˇghi
 7823
 7824           jk
 7825           nlmo
 7826           "#
 7827    ));
 7828
 7829    cx.update_editor(|editor, window, cx| {
 7830        editor.add_selection_above(&Default::default(), window, cx);
 7831    });
 7832
 7833    cx.assert_editor_state(indoc!(
 7834        r#"abcˇ
 7835           defˇghi
 7836
 7837           jk
 7838           nlmo
 7839           "#
 7840    ));
 7841
 7842    cx.update_editor(|editor, window, cx| {
 7843        editor.add_selection_above(&Default::default(), window, cx);
 7844    });
 7845
 7846    cx.assert_editor_state(indoc!(
 7847        r#"abcˇ
 7848            defˇghi
 7849
 7850            jk
 7851            nlmo
 7852            "#
 7853    ));
 7854
 7855    cx.update_editor(|editor, window, cx| {
 7856        editor.add_selection_below(&Default::default(), window, cx);
 7857    });
 7858
 7859    cx.assert_editor_state(indoc!(
 7860        r#"abc
 7861           defˇghi
 7862
 7863           jk
 7864           nlmo
 7865           "#
 7866    ));
 7867
 7868    cx.update_editor(|editor, window, cx| {
 7869        editor.undo_selection(&Default::default(), window, cx);
 7870    });
 7871
 7872    cx.assert_editor_state(indoc!(
 7873        r#"abcˇ
 7874           defˇghi
 7875
 7876           jk
 7877           nlmo
 7878           "#
 7879    ));
 7880
 7881    cx.update_editor(|editor, window, cx| {
 7882        editor.redo_selection(&Default::default(), window, cx);
 7883    });
 7884
 7885    cx.assert_editor_state(indoc!(
 7886        r#"abc
 7887           defˇghi
 7888
 7889           jk
 7890           nlmo
 7891           "#
 7892    ));
 7893
 7894    cx.update_editor(|editor, window, cx| {
 7895        editor.add_selection_below(&Default::default(), window, cx);
 7896    });
 7897
 7898    cx.assert_editor_state(indoc!(
 7899        r#"abc
 7900           defˇghi
 7901           ˇ
 7902           jk
 7903           nlmo
 7904           "#
 7905    ));
 7906
 7907    cx.update_editor(|editor, window, cx| {
 7908        editor.add_selection_below(&Default::default(), window, cx);
 7909    });
 7910
 7911    cx.assert_editor_state(indoc!(
 7912        r#"abc
 7913           defˇghi
 7914           ˇ
 7915           jkˇ
 7916           nlmo
 7917           "#
 7918    ));
 7919
 7920    cx.update_editor(|editor, window, cx| {
 7921        editor.add_selection_below(&Default::default(), window, cx);
 7922    });
 7923
 7924    cx.assert_editor_state(indoc!(
 7925        r#"abc
 7926           defˇghi
 7927           ˇ
 7928           jkˇ
 7929           nlmˇo
 7930           "#
 7931    ));
 7932
 7933    cx.update_editor(|editor, window, cx| {
 7934        editor.add_selection_below(&Default::default(), window, cx);
 7935    });
 7936
 7937    cx.assert_editor_state(indoc!(
 7938        r#"abc
 7939           defˇghi
 7940           ˇ
 7941           jkˇ
 7942           nlmˇo
 7943           ˇ"#
 7944    ));
 7945
 7946    // change selections
 7947    cx.set_state(indoc!(
 7948        r#"abc
 7949           def«ˇg»hi
 7950
 7951           jk
 7952           nlmo
 7953           "#
 7954    ));
 7955
 7956    cx.update_editor(|editor, window, cx| {
 7957        editor.add_selection_below(&Default::default(), window, cx);
 7958    });
 7959
 7960    cx.assert_editor_state(indoc!(
 7961        r#"abc
 7962           def«ˇg»hi
 7963
 7964           jk
 7965           nlm«ˇo»
 7966           "#
 7967    ));
 7968
 7969    cx.update_editor(|editor, window, cx| {
 7970        editor.add_selection_below(&Default::default(), window, cx);
 7971    });
 7972
 7973    cx.assert_editor_state(indoc!(
 7974        r#"abc
 7975           def«ˇg»hi
 7976
 7977           jk
 7978           nlm«ˇo»
 7979           "#
 7980    ));
 7981
 7982    cx.update_editor(|editor, window, cx| {
 7983        editor.add_selection_above(&Default::default(), window, cx);
 7984    });
 7985
 7986    cx.assert_editor_state(indoc!(
 7987        r#"abc
 7988           def«ˇg»hi
 7989
 7990           jk
 7991           nlmo
 7992           "#
 7993    ));
 7994
 7995    cx.update_editor(|editor, window, cx| {
 7996        editor.add_selection_above(&Default::default(), window, cx);
 7997    });
 7998
 7999    cx.assert_editor_state(indoc!(
 8000        r#"abc
 8001           def«ˇg»hi
 8002
 8003           jk
 8004           nlmo
 8005           "#
 8006    ));
 8007
 8008    // Change selections again
 8009    cx.set_state(indoc!(
 8010        r#"a«bc
 8011           defgˇ»hi
 8012
 8013           jk
 8014           nlmo
 8015           "#
 8016    ));
 8017
 8018    cx.update_editor(|editor, window, cx| {
 8019        editor.add_selection_below(&Default::default(), window, cx);
 8020    });
 8021
 8022    cx.assert_editor_state(indoc!(
 8023        r#"a«bcˇ»
 8024           d«efgˇ»hi
 8025
 8026           j«kˇ»
 8027           nlmo
 8028           "#
 8029    ));
 8030
 8031    cx.update_editor(|editor, window, cx| {
 8032        editor.add_selection_below(&Default::default(), window, cx);
 8033    });
 8034    cx.assert_editor_state(indoc!(
 8035        r#"a«bcˇ»
 8036           d«efgˇ»hi
 8037
 8038           j«kˇ»
 8039           n«lmoˇ»
 8040           "#
 8041    ));
 8042    cx.update_editor(|editor, window, cx| {
 8043        editor.add_selection_above(&Default::default(), window, cx);
 8044    });
 8045
 8046    cx.assert_editor_state(indoc!(
 8047        r#"a«bcˇ»
 8048           d«efgˇ»hi
 8049
 8050           j«kˇ»
 8051           nlmo
 8052           "#
 8053    ));
 8054
 8055    // Change selections again
 8056    cx.set_state(indoc!(
 8057        r#"abc
 8058           d«ˇefghi
 8059
 8060           jk
 8061           nlm»o
 8062           "#
 8063    ));
 8064
 8065    cx.update_editor(|editor, window, cx| {
 8066        editor.add_selection_above(&Default::default(), window, cx);
 8067    });
 8068
 8069    cx.assert_editor_state(indoc!(
 8070        r#"a«ˇbc»
 8071           d«ˇef»ghi
 8072
 8073           j«ˇk»
 8074           n«ˇlm»o
 8075           "#
 8076    ));
 8077
 8078    cx.update_editor(|editor, window, cx| {
 8079        editor.add_selection_below(&Default::default(), window, cx);
 8080    });
 8081
 8082    cx.assert_editor_state(indoc!(
 8083        r#"abc
 8084           d«ˇef»ghi
 8085
 8086           j«ˇk»
 8087           n«ˇlm»o
 8088           "#
 8089    ));
 8090}
 8091
 8092#[gpui::test]
 8093async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8094    init_test(cx, |_| {});
 8095    let mut cx = EditorTestContext::new(cx).await;
 8096
 8097    cx.set_state(indoc!(
 8098        r#"line onˇe
 8099           liˇne two
 8100           line three
 8101           line four"#
 8102    ));
 8103
 8104    cx.update_editor(|editor, window, cx| {
 8105        editor.add_selection_below(&Default::default(), window, cx);
 8106    });
 8107
 8108    // test multiple cursors expand in the same direction
 8109    cx.assert_editor_state(indoc!(
 8110        r#"line onˇe
 8111           liˇne twˇo
 8112           liˇne three
 8113           line four"#
 8114    ));
 8115
 8116    cx.update_editor(|editor, window, cx| {
 8117        editor.add_selection_below(&Default::default(), window, cx);
 8118    });
 8119
 8120    cx.update_editor(|editor, window, cx| {
 8121        editor.add_selection_below(&Default::default(), window, cx);
 8122    });
 8123
 8124    // test multiple cursors expand below overflow
 8125    cx.assert_editor_state(indoc!(
 8126        r#"line onˇe
 8127           liˇne twˇo
 8128           liˇne thˇree
 8129           liˇne foˇur"#
 8130    ));
 8131
 8132    cx.update_editor(|editor, window, cx| {
 8133        editor.add_selection_above(&Default::default(), window, cx);
 8134    });
 8135
 8136    // test multiple cursors retrieves back correctly
 8137    cx.assert_editor_state(indoc!(
 8138        r#"line onˇe
 8139           liˇne twˇo
 8140           liˇne thˇree
 8141           line four"#
 8142    ));
 8143
 8144    cx.update_editor(|editor, window, cx| {
 8145        editor.add_selection_above(&Default::default(), window, cx);
 8146    });
 8147
 8148    cx.update_editor(|editor, window, cx| {
 8149        editor.add_selection_above(&Default::default(), window, cx);
 8150    });
 8151
 8152    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8153    cx.assert_editor_state(indoc!(
 8154        r#"liˇne onˇe
 8155           liˇne two
 8156           line three
 8157           line four"#
 8158    ));
 8159
 8160    cx.update_editor(|editor, window, cx| {
 8161        editor.undo_selection(&Default::default(), window, cx);
 8162    });
 8163
 8164    // test undo
 8165    cx.assert_editor_state(indoc!(
 8166        r#"line onˇe
 8167           liˇne twˇo
 8168           line three
 8169           line four"#
 8170    ));
 8171
 8172    cx.update_editor(|editor, window, cx| {
 8173        editor.redo_selection(&Default::default(), window, cx);
 8174    });
 8175
 8176    // test redo
 8177    cx.assert_editor_state(indoc!(
 8178        r#"liˇne onˇe
 8179           liˇne two
 8180           line three
 8181           line four"#
 8182    ));
 8183
 8184    cx.set_state(indoc!(
 8185        r#"abcd
 8186           ef«ghˇ»
 8187           ijkl
 8188           «mˇ»nop"#
 8189    ));
 8190
 8191    cx.update_editor(|editor, window, cx| {
 8192        editor.add_selection_above(&Default::default(), window, cx);
 8193    });
 8194
 8195    // test multiple selections expand in the same direction
 8196    cx.assert_editor_state(indoc!(
 8197        r#"ab«cdˇ»
 8198           ef«ghˇ»
 8199           «iˇ»jkl
 8200           «mˇ»nop"#
 8201    ));
 8202
 8203    cx.update_editor(|editor, window, cx| {
 8204        editor.add_selection_above(&Default::default(), window, cx);
 8205    });
 8206
 8207    // test multiple selection upward overflow
 8208    cx.assert_editor_state(indoc!(
 8209        r#"ab«cdˇ»
 8210           «eˇ»f«ghˇ»
 8211           «iˇ»jkl
 8212           «mˇ»nop"#
 8213    ));
 8214
 8215    cx.update_editor(|editor, window, cx| {
 8216        editor.add_selection_below(&Default::default(), window, cx);
 8217    });
 8218
 8219    // test multiple selection retrieves back correctly
 8220    cx.assert_editor_state(indoc!(
 8221        r#"abcd
 8222           ef«ghˇ»
 8223           «iˇ»jkl
 8224           «mˇ»nop"#
 8225    ));
 8226
 8227    cx.update_editor(|editor, window, cx| {
 8228        editor.add_selection_below(&Default::default(), window, cx);
 8229    });
 8230
 8231    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8232    cx.assert_editor_state(indoc!(
 8233        r#"abcd
 8234           ef«ghˇ»
 8235           ij«klˇ»
 8236           «mˇ»nop"#
 8237    ));
 8238
 8239    cx.update_editor(|editor, window, cx| {
 8240        editor.undo_selection(&Default::default(), window, cx);
 8241    });
 8242
 8243    // test undo
 8244    cx.assert_editor_state(indoc!(
 8245        r#"abcd
 8246           ef«ghˇ»
 8247           «iˇ»jkl
 8248           «mˇ»nop"#
 8249    ));
 8250
 8251    cx.update_editor(|editor, window, cx| {
 8252        editor.redo_selection(&Default::default(), window, cx);
 8253    });
 8254
 8255    // test redo
 8256    cx.assert_editor_state(indoc!(
 8257        r#"abcd
 8258           ef«ghˇ»
 8259           ij«klˇ»
 8260           «mˇ»nop"#
 8261    ));
 8262}
 8263
 8264#[gpui::test]
 8265async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8266    init_test(cx, |_| {});
 8267    let mut cx = EditorTestContext::new(cx).await;
 8268
 8269    cx.set_state(indoc!(
 8270        r#"line onˇe
 8271           liˇne two
 8272           line three
 8273           line four"#
 8274    ));
 8275
 8276    cx.update_editor(|editor, window, cx| {
 8277        editor.add_selection_below(&Default::default(), window, cx);
 8278        editor.add_selection_below(&Default::default(), window, cx);
 8279        editor.add_selection_below(&Default::default(), window, cx);
 8280    });
 8281
 8282    // initial state with two multi cursor groups
 8283    cx.assert_editor_state(indoc!(
 8284        r#"line onˇe
 8285           liˇne twˇo
 8286           liˇne thˇree
 8287           liˇne foˇur"#
 8288    ));
 8289
 8290    // add single cursor in middle - simulate opt click
 8291    cx.update_editor(|editor, window, cx| {
 8292        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8293        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8294        editor.end_selection(window, cx);
 8295    });
 8296
 8297    cx.assert_editor_state(indoc!(
 8298        r#"line onˇe
 8299           liˇne twˇo
 8300           liˇneˇ thˇree
 8301           liˇne foˇur"#
 8302    ));
 8303
 8304    cx.update_editor(|editor, window, cx| {
 8305        editor.add_selection_above(&Default::default(), window, cx);
 8306    });
 8307
 8308    // test new added selection expands above and existing selection shrinks
 8309    cx.assert_editor_state(indoc!(
 8310        r#"line onˇe
 8311           liˇneˇ twˇo
 8312           liˇneˇ thˇree
 8313           line four"#
 8314    ));
 8315
 8316    cx.update_editor(|editor, window, cx| {
 8317        editor.add_selection_above(&Default::default(), window, cx);
 8318    });
 8319
 8320    // test new added selection expands above and existing selection shrinks
 8321    cx.assert_editor_state(indoc!(
 8322        r#"lineˇ onˇe
 8323           liˇneˇ twˇo
 8324           lineˇ three
 8325           line four"#
 8326    ));
 8327
 8328    // intial state with two selection groups
 8329    cx.set_state(indoc!(
 8330        r#"abcd
 8331           ef«ghˇ»
 8332           ijkl
 8333           «mˇ»nop"#
 8334    ));
 8335
 8336    cx.update_editor(|editor, window, cx| {
 8337        editor.add_selection_above(&Default::default(), window, cx);
 8338        editor.add_selection_above(&Default::default(), window, cx);
 8339    });
 8340
 8341    cx.assert_editor_state(indoc!(
 8342        r#"ab«cdˇ»
 8343           «eˇ»f«ghˇ»
 8344           «iˇ»jkl
 8345           «mˇ»nop"#
 8346    ));
 8347
 8348    // add single selection in middle - simulate opt drag
 8349    cx.update_editor(|editor, window, cx| {
 8350        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8351        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8352        editor.update_selection(
 8353            DisplayPoint::new(DisplayRow(2), 4),
 8354            0,
 8355            gpui::Point::<f32>::default(),
 8356            window,
 8357            cx,
 8358        );
 8359        editor.end_selection(window, cx);
 8360    });
 8361
 8362    cx.assert_editor_state(indoc!(
 8363        r#"ab«cdˇ»
 8364           «eˇ»f«ghˇ»
 8365           «iˇ»jk«lˇ»
 8366           «mˇ»nop"#
 8367    ));
 8368
 8369    cx.update_editor(|editor, window, cx| {
 8370        editor.add_selection_below(&Default::default(), window, cx);
 8371    });
 8372
 8373    // test new added selection expands below, others shrinks from above
 8374    cx.assert_editor_state(indoc!(
 8375        r#"abcd
 8376           ef«ghˇ»
 8377           «iˇ»jk«lˇ»
 8378           «mˇ»no«pˇ»"#
 8379    ));
 8380}
 8381
 8382#[gpui::test]
 8383async fn test_select_next(cx: &mut TestAppContext) {
 8384    init_test(cx, |_| {});
 8385    let mut cx = EditorTestContext::new(cx).await;
 8386
 8387    // Enable case sensitive search.
 8388    update_test_editor_settings(&mut cx, |settings| {
 8389        let mut search_settings = SearchSettingsContent::default();
 8390        search_settings.case_sensitive = Some(true);
 8391        settings.search = Some(search_settings);
 8392    });
 8393
 8394    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8395
 8396    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8397        .unwrap();
 8398    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8399
 8400    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8401        .unwrap();
 8402    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8403
 8404    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8405    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8406
 8407    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8408    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8409
 8410    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8411        .unwrap();
 8412    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8413
 8414    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8415        .unwrap();
 8416    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8417
 8418    // Test selection direction should be preserved
 8419    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8420
 8421    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8422        .unwrap();
 8423    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8424
 8425    // Test case sensitivity
 8426    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8427    cx.update_editor(|e, window, cx| {
 8428        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8429    });
 8430    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8431
 8432    // Disable case sensitive search.
 8433    update_test_editor_settings(&mut cx, |settings| {
 8434        let mut search_settings = SearchSettingsContent::default();
 8435        search_settings.case_sensitive = Some(false);
 8436        settings.search = Some(search_settings);
 8437    });
 8438
 8439    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8440    cx.update_editor(|e, window, cx| {
 8441        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8442        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8443    });
 8444    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8445}
 8446
 8447#[gpui::test]
 8448async fn test_select_all_matches(cx: &mut TestAppContext) {
 8449    init_test(cx, |_| {});
 8450    let mut cx = EditorTestContext::new(cx).await;
 8451
 8452    // Enable case sensitive search.
 8453    update_test_editor_settings(&mut cx, |settings| {
 8454        let mut search_settings = SearchSettingsContent::default();
 8455        search_settings.case_sensitive = Some(true);
 8456        settings.search = Some(search_settings);
 8457    });
 8458
 8459    // Test caret-only selections
 8460    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8461    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8462        .unwrap();
 8463    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8464
 8465    // Test left-to-right selections
 8466    cx.set_state("abc\n«abcˇ»\nabc");
 8467    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8468        .unwrap();
 8469    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8470
 8471    // Test right-to-left selections
 8472    cx.set_state("abc\n«ˇabc»\nabc");
 8473    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8474        .unwrap();
 8475    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8476
 8477    // Test selecting whitespace with caret selection
 8478    cx.set_state("abc\nˇ   abc\nabc");
 8479    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8480        .unwrap();
 8481    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8482
 8483    // Test selecting whitespace with left-to-right selection
 8484    cx.set_state("abc\n«ˇ  »abc\nabc");
 8485    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8486        .unwrap();
 8487    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8488
 8489    // Test no matches with right-to-left selection
 8490    cx.set_state("abc\n«  ˇ»abc\nabc");
 8491    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8492        .unwrap();
 8493    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8494
 8495    // Test with a single word and clip_at_line_ends=true (#29823)
 8496    cx.set_state("aˇbc");
 8497    cx.update_editor(|e, window, cx| {
 8498        e.set_clip_at_line_ends(true, cx);
 8499        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8500        e.set_clip_at_line_ends(false, cx);
 8501    });
 8502    cx.assert_editor_state("«abcˇ»");
 8503
 8504    // Test case sensitivity
 8505    cx.set_state("fˇoo\nFOO\nFoo");
 8506    cx.update_editor(|e, window, cx| {
 8507        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8508    });
 8509    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8510
 8511    // Disable case sensitive search.
 8512    update_test_editor_settings(&mut cx, |settings| {
 8513        let mut search_settings = SearchSettingsContent::default();
 8514        search_settings.case_sensitive = Some(false);
 8515        settings.search = Some(search_settings);
 8516    });
 8517
 8518    cx.set_state("fˇoo\nFOO\nFoo");
 8519    cx.update_editor(|e, window, cx| {
 8520        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8521    });
 8522    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8523}
 8524
 8525#[gpui::test]
 8526async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8527    init_test(cx, |_| {});
 8528
 8529    let mut cx = EditorTestContext::new(cx).await;
 8530
 8531    let large_body_1 = "\nd".repeat(200);
 8532    let large_body_2 = "\ne".repeat(200);
 8533
 8534    cx.set_state(&format!(
 8535        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8536    ));
 8537    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8538        let scroll_position = editor.scroll_position(cx);
 8539        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8540        scroll_position
 8541    });
 8542
 8543    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8544        .unwrap();
 8545    cx.assert_editor_state(&format!(
 8546        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8547    ));
 8548    let scroll_position_after_selection =
 8549        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8550    assert_eq!(
 8551        initial_scroll_position, scroll_position_after_selection,
 8552        "Scroll position should not change after selecting all matches"
 8553    );
 8554}
 8555
 8556#[gpui::test]
 8557async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8558    init_test(cx, |_| {});
 8559
 8560    let mut cx = EditorLspTestContext::new_rust(
 8561        lsp::ServerCapabilities {
 8562            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8563            ..Default::default()
 8564        },
 8565        cx,
 8566    )
 8567    .await;
 8568
 8569    cx.set_state(indoc! {"
 8570        line 1
 8571        line 2
 8572        linˇe 3
 8573        line 4
 8574        line 5
 8575    "});
 8576
 8577    // Make an edit
 8578    cx.update_editor(|editor, window, cx| {
 8579        editor.handle_input("X", window, cx);
 8580    });
 8581
 8582    // Move cursor to a different position
 8583    cx.update_editor(|editor, window, cx| {
 8584        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8585            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8586        });
 8587    });
 8588
 8589    cx.assert_editor_state(indoc! {"
 8590        line 1
 8591        line 2
 8592        linXe 3
 8593        line 4
 8594        liˇne 5
 8595    "});
 8596
 8597    cx.lsp
 8598        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8599            Ok(Some(vec![lsp::TextEdit::new(
 8600                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8601                "PREFIX ".to_string(),
 8602            )]))
 8603        });
 8604
 8605    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8606        .unwrap()
 8607        .await
 8608        .unwrap();
 8609
 8610    cx.assert_editor_state(indoc! {"
 8611        PREFIX line 1
 8612        line 2
 8613        linXe 3
 8614        line 4
 8615        liˇne 5
 8616    "});
 8617
 8618    // Undo formatting
 8619    cx.update_editor(|editor, window, cx| {
 8620        editor.undo(&Default::default(), window, cx);
 8621    });
 8622
 8623    // Verify cursor moved back to position after edit
 8624    cx.assert_editor_state(indoc! {"
 8625        line 1
 8626        line 2
 8627        linXˇe 3
 8628        line 4
 8629        line 5
 8630    "});
 8631}
 8632
 8633#[gpui::test]
 8634async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8635    init_test(cx, |_| {});
 8636
 8637    let mut cx = EditorTestContext::new(cx).await;
 8638
 8639    let provider = cx.new(|_| FakeEditPredictionDelegate::default());
 8640    cx.update_editor(|editor, window, cx| {
 8641        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8642    });
 8643
 8644    cx.set_state(indoc! {"
 8645        line 1
 8646        line 2
 8647        linˇe 3
 8648        line 4
 8649        line 5
 8650        line 6
 8651        line 7
 8652        line 8
 8653        line 9
 8654        line 10
 8655    "});
 8656
 8657    let snapshot = cx.buffer_snapshot();
 8658    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8659
 8660    cx.update(|_, cx| {
 8661        provider.update(cx, |provider, _| {
 8662            provider.set_edit_prediction(Some(edit_prediction_types::EditPrediction::Local {
 8663                id: None,
 8664                edits: vec![(edit_position..edit_position, "X".into())],
 8665                edit_preview: None,
 8666            }))
 8667        })
 8668    });
 8669
 8670    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8671    cx.update_editor(|editor, window, cx| {
 8672        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8673    });
 8674
 8675    cx.assert_editor_state(indoc! {"
 8676        line 1
 8677        line 2
 8678        lineXˇ 3
 8679        line 4
 8680        line 5
 8681        line 6
 8682        line 7
 8683        line 8
 8684        line 9
 8685        line 10
 8686    "});
 8687
 8688    cx.update_editor(|editor, window, cx| {
 8689        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8690            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8691        });
 8692    });
 8693
 8694    cx.assert_editor_state(indoc! {"
 8695        line 1
 8696        line 2
 8697        lineX 3
 8698        line 4
 8699        line 5
 8700        line 6
 8701        line 7
 8702        line 8
 8703        line 9
 8704        liˇne 10
 8705    "});
 8706
 8707    cx.update_editor(|editor, window, cx| {
 8708        editor.undo(&Default::default(), window, cx);
 8709    });
 8710
 8711    cx.assert_editor_state(indoc! {"
 8712        line 1
 8713        line 2
 8714        lineˇ 3
 8715        line 4
 8716        line 5
 8717        line 6
 8718        line 7
 8719        line 8
 8720        line 9
 8721        line 10
 8722    "});
 8723}
 8724
 8725#[gpui::test]
 8726async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8727    init_test(cx, |_| {});
 8728
 8729    let mut cx = EditorTestContext::new(cx).await;
 8730    cx.set_state(
 8731        r#"let foo = 2;
 8732lˇet foo = 2;
 8733let fooˇ = 2;
 8734let foo = 2;
 8735let foo = ˇ2;"#,
 8736    );
 8737
 8738    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8739        .unwrap();
 8740    cx.assert_editor_state(
 8741        r#"let foo = 2;
 8742«letˇ» foo = 2;
 8743let «fooˇ» = 2;
 8744let foo = 2;
 8745let foo = «2ˇ»;"#,
 8746    );
 8747
 8748    // noop for multiple selections with different contents
 8749    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8750        .unwrap();
 8751    cx.assert_editor_state(
 8752        r#"let foo = 2;
 8753«letˇ» foo = 2;
 8754let «fooˇ» = 2;
 8755let foo = 2;
 8756let foo = «2ˇ»;"#,
 8757    );
 8758
 8759    // Test last selection direction should be preserved
 8760    cx.set_state(
 8761        r#"let foo = 2;
 8762let foo = 2;
 8763let «fooˇ» = 2;
 8764let «ˇfoo» = 2;
 8765let foo = 2;"#,
 8766    );
 8767
 8768    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8769        .unwrap();
 8770    cx.assert_editor_state(
 8771        r#"let foo = 2;
 8772let foo = 2;
 8773let «fooˇ» = 2;
 8774let «ˇfoo» = 2;
 8775let «ˇfoo» = 2;"#,
 8776    );
 8777}
 8778
 8779#[gpui::test]
 8780async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8781    init_test(cx, |_| {});
 8782
 8783    let mut cx =
 8784        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8785
 8786    cx.assert_editor_state(indoc! {"
 8787        ˇbbb
 8788        ccc
 8789
 8790        bbb
 8791        ccc
 8792        "});
 8793    cx.dispatch_action(SelectPrevious::default());
 8794    cx.assert_editor_state(indoc! {"
 8795                «bbbˇ»
 8796                ccc
 8797
 8798                bbb
 8799                ccc
 8800                "});
 8801    cx.dispatch_action(SelectPrevious::default());
 8802    cx.assert_editor_state(indoc! {"
 8803                «bbbˇ»
 8804                ccc
 8805
 8806                «bbbˇ»
 8807                ccc
 8808                "});
 8809}
 8810
 8811#[gpui::test]
 8812async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8813    init_test(cx, |_| {});
 8814
 8815    let mut cx = EditorTestContext::new(cx).await;
 8816    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8817
 8818    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8819        .unwrap();
 8820    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8821
 8822    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8823        .unwrap();
 8824    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8825
 8826    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8827    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8828
 8829    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8830    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8831
 8832    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8833        .unwrap();
 8834    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8835
 8836    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8837        .unwrap();
 8838    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8839}
 8840
 8841#[gpui::test]
 8842async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8843    init_test(cx, |_| {});
 8844
 8845    let mut cx = EditorTestContext::new(cx).await;
 8846    cx.set_state("");
 8847
 8848    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8849        .unwrap();
 8850    cx.assert_editor_state("«aˇ»");
 8851    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8852        .unwrap();
 8853    cx.assert_editor_state("«aˇ»");
 8854}
 8855
 8856#[gpui::test]
 8857async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8858    init_test(cx, |_| {});
 8859
 8860    let mut cx = EditorTestContext::new(cx).await;
 8861    cx.set_state(
 8862        r#"let foo = 2;
 8863lˇet foo = 2;
 8864let fooˇ = 2;
 8865let foo = 2;
 8866let foo = ˇ2;"#,
 8867    );
 8868
 8869    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8870        .unwrap();
 8871    cx.assert_editor_state(
 8872        r#"let foo = 2;
 8873«letˇ» foo = 2;
 8874let «fooˇ» = 2;
 8875let foo = 2;
 8876let foo = «2ˇ»;"#,
 8877    );
 8878
 8879    // noop for multiple selections with different contents
 8880    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8881        .unwrap();
 8882    cx.assert_editor_state(
 8883        r#"let foo = 2;
 8884«letˇ» foo = 2;
 8885let «fooˇ» = 2;
 8886let foo = 2;
 8887let foo = «2ˇ»;"#,
 8888    );
 8889}
 8890
 8891#[gpui::test]
 8892async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8893    init_test(cx, |_| {});
 8894    let mut cx = EditorTestContext::new(cx).await;
 8895
 8896    // Enable case sensitive search.
 8897    update_test_editor_settings(&mut cx, |settings| {
 8898        let mut search_settings = SearchSettingsContent::default();
 8899        search_settings.case_sensitive = Some(true);
 8900        settings.search = Some(search_settings);
 8901    });
 8902
 8903    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8904
 8905    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8906        .unwrap();
 8907    // selection direction is preserved
 8908    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8909
 8910    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8911        .unwrap();
 8912    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8913
 8914    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8915    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8916
 8917    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8918    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8919
 8920    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8921        .unwrap();
 8922    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8923
 8924    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8925        .unwrap();
 8926    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8927
 8928    // Test case sensitivity
 8929    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 8930    cx.update_editor(|e, window, cx| {
 8931        e.select_previous(&SelectPrevious::default(), window, cx)
 8932            .unwrap();
 8933        e.select_previous(&SelectPrevious::default(), window, cx)
 8934            .unwrap();
 8935    });
 8936    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8937
 8938    // Disable case sensitive search.
 8939    update_test_editor_settings(&mut cx, |settings| {
 8940        let mut search_settings = SearchSettingsContent::default();
 8941        search_settings.case_sensitive = Some(false);
 8942        settings.search = Some(search_settings);
 8943    });
 8944
 8945    cx.set_state("foo\nFOO\n«ˇFoo»");
 8946    cx.update_editor(|e, window, cx| {
 8947        e.select_previous(&SelectPrevious::default(), window, cx)
 8948            .unwrap();
 8949        e.select_previous(&SelectPrevious::default(), window, cx)
 8950            .unwrap();
 8951    });
 8952    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8953}
 8954
 8955#[gpui::test]
 8956async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8957    init_test(cx, |_| {});
 8958
 8959    let language = Arc::new(Language::new(
 8960        LanguageConfig::default(),
 8961        Some(tree_sitter_rust::LANGUAGE.into()),
 8962    ));
 8963
 8964    let text = r#"
 8965        use mod1::mod2::{mod3, mod4};
 8966
 8967        fn fn_1(param1: bool, param2: &str) {
 8968            let var1 = "text";
 8969        }
 8970    "#
 8971    .unindent();
 8972
 8973    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 8974    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8975    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8976
 8977    editor
 8978        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8979        .await;
 8980
 8981    editor.update_in(cx, |editor, window, cx| {
 8982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8983            s.select_display_ranges([
 8984                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8985                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8986                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8987            ]);
 8988        });
 8989        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8990    });
 8991    editor.update(cx, |editor, cx| {
 8992        assert_text_with_selections(
 8993            editor,
 8994            indoc! {r#"
 8995                use mod1::mod2::{mod3, «mod4ˇ»};
 8996
 8997                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8998                    let var1 = "«ˇtext»";
 8999                }
 9000            "#},
 9001            cx,
 9002        );
 9003    });
 9004
 9005    editor.update_in(cx, |editor, window, cx| {
 9006        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9007    });
 9008    editor.update(cx, |editor, cx| {
 9009        assert_text_with_selections(
 9010            editor,
 9011            indoc! {r#"
 9012                use mod1::mod2::«{mod3, mod4}ˇ»;
 9013
 9014                «ˇfn fn_1(param1: bool, param2: &str) {
 9015                    let var1 = "text";
 9016 9017            "#},
 9018            cx,
 9019        );
 9020    });
 9021
 9022    editor.update_in(cx, |editor, window, cx| {
 9023        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9024    });
 9025    assert_eq!(
 9026        editor.update(cx, |editor, cx| editor
 9027            .selections
 9028            .display_ranges(&editor.display_snapshot(cx))),
 9029        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9030    );
 9031
 9032    // Trying to expand the selected syntax node one more time has no effect.
 9033    editor.update_in(cx, |editor, window, cx| {
 9034        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9035    });
 9036    assert_eq!(
 9037        editor.update(cx, |editor, cx| editor
 9038            .selections
 9039            .display_ranges(&editor.display_snapshot(cx))),
 9040        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9041    );
 9042
 9043    editor.update_in(cx, |editor, window, cx| {
 9044        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9045    });
 9046    editor.update(cx, |editor, cx| {
 9047        assert_text_with_selections(
 9048            editor,
 9049            indoc! {r#"
 9050                use mod1::mod2::«{mod3, mod4}ˇ»;
 9051
 9052                «ˇfn fn_1(param1: bool, param2: &str) {
 9053                    let var1 = "text";
 9054 9055            "#},
 9056            cx,
 9057        );
 9058    });
 9059
 9060    editor.update_in(cx, |editor, window, cx| {
 9061        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9062    });
 9063    editor.update(cx, |editor, cx| {
 9064        assert_text_with_selections(
 9065            editor,
 9066            indoc! {r#"
 9067                use mod1::mod2::{mod3, «mod4ˇ»};
 9068
 9069                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9070                    let var1 = "«ˇtext»";
 9071                }
 9072            "#},
 9073            cx,
 9074        );
 9075    });
 9076
 9077    editor.update_in(cx, |editor, window, cx| {
 9078        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9079    });
 9080    editor.update(cx, |editor, cx| {
 9081        assert_text_with_selections(
 9082            editor,
 9083            indoc! {r#"
 9084                use mod1::mod2::{mod3, moˇd4};
 9085
 9086                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9087                    let var1 = "teˇxt";
 9088                }
 9089            "#},
 9090            cx,
 9091        );
 9092    });
 9093
 9094    // Trying to shrink the selected syntax node one more time has no effect.
 9095    editor.update_in(cx, |editor, window, cx| {
 9096        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9097    });
 9098    editor.update_in(cx, |editor, _, cx| {
 9099        assert_text_with_selections(
 9100            editor,
 9101            indoc! {r#"
 9102                use mod1::mod2::{mod3, moˇd4};
 9103
 9104                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9105                    let var1 = "teˇxt";
 9106                }
 9107            "#},
 9108            cx,
 9109        );
 9110    });
 9111
 9112    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9113    // a fold.
 9114    editor.update_in(cx, |editor, window, cx| {
 9115        editor.fold_creases(
 9116            vec![
 9117                Crease::simple(
 9118                    Point::new(0, 21)..Point::new(0, 24),
 9119                    FoldPlaceholder::test(),
 9120                ),
 9121                Crease::simple(
 9122                    Point::new(3, 20)..Point::new(3, 22),
 9123                    FoldPlaceholder::test(),
 9124                ),
 9125            ],
 9126            true,
 9127            window,
 9128            cx,
 9129        );
 9130        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9131    });
 9132    editor.update(cx, |editor, cx| {
 9133        assert_text_with_selections(
 9134            editor,
 9135            indoc! {r#"
 9136                use mod1::mod2::«{mod3, mod4}ˇ»;
 9137
 9138                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9139                    let var1 = "«ˇtext»";
 9140                }
 9141            "#},
 9142            cx,
 9143        );
 9144    });
 9145}
 9146
 9147#[gpui::test]
 9148async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9149    init_test(cx, |_| {});
 9150
 9151    let language = Arc::new(Language::new(
 9152        LanguageConfig::default(),
 9153        Some(tree_sitter_rust::LANGUAGE.into()),
 9154    ));
 9155
 9156    let text = "let a = 2;";
 9157
 9158    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9159    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9160    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9161
 9162    editor
 9163        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9164        .await;
 9165
 9166    // Test case 1: Cursor at end of word
 9167    editor.update_in(cx, |editor, window, cx| {
 9168        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9169            s.select_display_ranges([
 9170                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9171            ]);
 9172        });
 9173    });
 9174    editor.update(cx, |editor, cx| {
 9175        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9176    });
 9177    editor.update_in(cx, |editor, window, cx| {
 9178        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9179    });
 9180    editor.update(cx, |editor, cx| {
 9181        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9182    });
 9183    editor.update_in(cx, |editor, window, cx| {
 9184        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9185    });
 9186    editor.update(cx, |editor, cx| {
 9187        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9188    });
 9189
 9190    // Test case 2: Cursor at end of statement
 9191    editor.update_in(cx, |editor, window, cx| {
 9192        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9193            s.select_display_ranges([
 9194                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9195            ]);
 9196        });
 9197    });
 9198    editor.update(cx, |editor, cx| {
 9199        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9200    });
 9201    editor.update_in(cx, |editor, window, cx| {
 9202        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9203    });
 9204    editor.update(cx, |editor, cx| {
 9205        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9206    });
 9207}
 9208
 9209#[gpui::test]
 9210async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9211    init_test(cx, |_| {});
 9212
 9213    let language = Arc::new(Language::new(
 9214        LanguageConfig {
 9215            name: "JavaScript".into(),
 9216            ..Default::default()
 9217        },
 9218        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9219    ));
 9220
 9221    let text = r#"
 9222        let a = {
 9223            key: "value",
 9224        };
 9225    "#
 9226    .unindent();
 9227
 9228    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9229    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9230    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9231
 9232    editor
 9233        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9234        .await;
 9235
 9236    // Test case 1: Cursor after '{'
 9237    editor.update_in(cx, |editor, window, cx| {
 9238        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9239            s.select_display_ranges([
 9240                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9241            ]);
 9242        });
 9243    });
 9244    editor.update(cx, |editor, cx| {
 9245        assert_text_with_selections(
 9246            editor,
 9247            indoc! {r#"
 9248                let a = {ˇ
 9249                    key: "value",
 9250                };
 9251            "#},
 9252            cx,
 9253        );
 9254    });
 9255    editor.update_in(cx, |editor, window, cx| {
 9256        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9257    });
 9258    editor.update(cx, |editor, cx| {
 9259        assert_text_with_selections(
 9260            editor,
 9261            indoc! {r#"
 9262                let a = «ˇ{
 9263                    key: "value",
 9264                }»;
 9265            "#},
 9266            cx,
 9267        );
 9268    });
 9269
 9270    // Test case 2: Cursor after ':'
 9271    editor.update_in(cx, |editor, window, cx| {
 9272        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9273            s.select_display_ranges([
 9274                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9275            ]);
 9276        });
 9277    });
 9278    editor.update(cx, |editor, cx| {
 9279        assert_text_with_selections(
 9280            editor,
 9281            indoc! {r#"
 9282                let a = {
 9283                    key:ˇ "value",
 9284                };
 9285            "#},
 9286            cx,
 9287        );
 9288    });
 9289    editor.update_in(cx, |editor, window, cx| {
 9290        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9291    });
 9292    editor.update(cx, |editor, cx| {
 9293        assert_text_with_selections(
 9294            editor,
 9295            indoc! {r#"
 9296                let a = {
 9297                    «ˇkey: "value"»,
 9298                };
 9299            "#},
 9300            cx,
 9301        );
 9302    });
 9303    editor.update_in(cx, |editor, window, cx| {
 9304        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9305    });
 9306    editor.update(cx, |editor, cx| {
 9307        assert_text_with_selections(
 9308            editor,
 9309            indoc! {r#"
 9310                let a = «ˇ{
 9311                    key: "value",
 9312                }»;
 9313            "#},
 9314            cx,
 9315        );
 9316    });
 9317
 9318    // Test case 3: Cursor after ','
 9319    editor.update_in(cx, |editor, window, cx| {
 9320        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9321            s.select_display_ranges([
 9322                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9323            ]);
 9324        });
 9325    });
 9326    editor.update(cx, |editor, cx| {
 9327        assert_text_with_selections(
 9328            editor,
 9329            indoc! {r#"
 9330                let a = {
 9331                    key: "value",ˇ
 9332                };
 9333            "#},
 9334            cx,
 9335        );
 9336    });
 9337    editor.update_in(cx, |editor, window, cx| {
 9338        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9339    });
 9340    editor.update(cx, |editor, cx| {
 9341        assert_text_with_selections(
 9342            editor,
 9343            indoc! {r#"
 9344                let a = «ˇ{
 9345                    key: "value",
 9346                }»;
 9347            "#},
 9348            cx,
 9349        );
 9350    });
 9351
 9352    // Test case 4: Cursor after ';'
 9353    editor.update_in(cx, |editor, window, cx| {
 9354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9355            s.select_display_ranges([
 9356                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9357            ]);
 9358        });
 9359    });
 9360    editor.update(cx, |editor, cx| {
 9361        assert_text_with_selections(
 9362            editor,
 9363            indoc! {r#"
 9364                let a = {
 9365                    key: "value",
 9366                };ˇ
 9367            "#},
 9368            cx,
 9369        );
 9370    });
 9371    editor.update_in(cx, |editor, window, cx| {
 9372        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9373    });
 9374    editor.update(cx, |editor, cx| {
 9375        assert_text_with_selections(
 9376            editor,
 9377            indoc! {r#"
 9378                «ˇlet a = {
 9379                    key: "value",
 9380                };
 9381                »"#},
 9382            cx,
 9383        );
 9384    });
 9385}
 9386
 9387#[gpui::test]
 9388async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9389    init_test(cx, |_| {});
 9390
 9391    let language = Arc::new(Language::new(
 9392        LanguageConfig::default(),
 9393        Some(tree_sitter_rust::LANGUAGE.into()),
 9394    ));
 9395
 9396    let text = r#"
 9397        use mod1::mod2::{mod3, mod4};
 9398
 9399        fn fn_1(param1: bool, param2: &str) {
 9400            let var1 = "hello world";
 9401        }
 9402    "#
 9403    .unindent();
 9404
 9405    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9406    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9407    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9408
 9409    editor
 9410        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9411        .await;
 9412
 9413    // Test 1: Cursor on a letter of a string word
 9414    editor.update_in(cx, |editor, window, cx| {
 9415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9416            s.select_display_ranges([
 9417                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9418            ]);
 9419        });
 9420    });
 9421    editor.update_in(cx, |editor, window, cx| {
 9422        assert_text_with_selections(
 9423            editor,
 9424            indoc! {r#"
 9425                use mod1::mod2::{mod3, mod4};
 9426
 9427                fn fn_1(param1: bool, param2: &str) {
 9428                    let var1 = "hˇello world";
 9429                }
 9430            "#},
 9431            cx,
 9432        );
 9433        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9434        assert_text_with_selections(
 9435            editor,
 9436            indoc! {r#"
 9437                use mod1::mod2::{mod3, mod4};
 9438
 9439                fn fn_1(param1: bool, param2: &str) {
 9440                    let var1 = "«ˇhello» world";
 9441                }
 9442            "#},
 9443            cx,
 9444        );
 9445    });
 9446
 9447    // Test 2: Partial selection within a word
 9448    editor.update_in(cx, |editor, window, cx| {
 9449        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9450            s.select_display_ranges([
 9451                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9452            ]);
 9453        });
 9454    });
 9455    editor.update_in(cx, |editor, window, cx| {
 9456        assert_text_with_selections(
 9457            editor,
 9458            indoc! {r#"
 9459                use mod1::mod2::{mod3, mod4};
 9460
 9461                fn fn_1(param1: bool, param2: &str) {
 9462                    let var1 = "h«elˇ»lo world";
 9463                }
 9464            "#},
 9465            cx,
 9466        );
 9467        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9468        assert_text_with_selections(
 9469            editor,
 9470            indoc! {r#"
 9471                use mod1::mod2::{mod3, mod4};
 9472
 9473                fn fn_1(param1: bool, param2: &str) {
 9474                    let var1 = "«ˇhello» world";
 9475                }
 9476            "#},
 9477            cx,
 9478        );
 9479    });
 9480
 9481    // Test 3: Complete word already selected
 9482    editor.update_in(cx, |editor, window, cx| {
 9483        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9484            s.select_display_ranges([
 9485                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9486            ]);
 9487        });
 9488    });
 9489    editor.update_in(cx, |editor, window, cx| {
 9490        assert_text_with_selections(
 9491            editor,
 9492            indoc! {r#"
 9493                use mod1::mod2::{mod3, mod4};
 9494
 9495                fn fn_1(param1: bool, param2: &str) {
 9496                    let var1 = "«helloˇ» world";
 9497                }
 9498            "#},
 9499            cx,
 9500        );
 9501        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9502        assert_text_with_selections(
 9503            editor,
 9504            indoc! {r#"
 9505                use mod1::mod2::{mod3, mod4};
 9506
 9507                fn fn_1(param1: bool, param2: &str) {
 9508                    let var1 = "«hello worldˇ»";
 9509                }
 9510            "#},
 9511            cx,
 9512        );
 9513    });
 9514
 9515    // Test 4: Selection spanning across words
 9516    editor.update_in(cx, |editor, window, cx| {
 9517        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9518            s.select_display_ranges([
 9519                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9520            ]);
 9521        });
 9522    });
 9523    editor.update_in(cx, |editor, window, cx| {
 9524        assert_text_with_selections(
 9525            editor,
 9526            indoc! {r#"
 9527                use mod1::mod2::{mod3, mod4};
 9528
 9529                fn fn_1(param1: bool, param2: &str) {
 9530                    let var1 = "hel«lo woˇ»rld";
 9531                }
 9532            "#},
 9533            cx,
 9534        );
 9535        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9536        assert_text_with_selections(
 9537            editor,
 9538            indoc! {r#"
 9539                use mod1::mod2::{mod3, mod4};
 9540
 9541                fn fn_1(param1: bool, param2: &str) {
 9542                    let var1 = "«ˇhello world»";
 9543                }
 9544            "#},
 9545            cx,
 9546        );
 9547    });
 9548
 9549    // Test 5: Expansion beyond string
 9550    editor.update_in(cx, |editor, window, cx| {
 9551        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9552        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9553        assert_text_with_selections(
 9554            editor,
 9555            indoc! {r#"
 9556                use mod1::mod2::{mod3, mod4};
 9557
 9558                fn fn_1(param1: bool, param2: &str) {
 9559                    «ˇlet var1 = "hello world";»
 9560                }
 9561            "#},
 9562            cx,
 9563        );
 9564    });
 9565}
 9566
 9567#[gpui::test]
 9568async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9569    init_test(cx, |_| {});
 9570
 9571    let mut cx = EditorTestContext::new(cx).await;
 9572
 9573    let language = Arc::new(Language::new(
 9574        LanguageConfig::default(),
 9575        Some(tree_sitter_rust::LANGUAGE.into()),
 9576    ));
 9577
 9578    cx.update_buffer(|buffer, cx| {
 9579        buffer.set_language(Some(language), cx);
 9580    });
 9581
 9582    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9583    cx.update_editor(|editor, window, cx| {
 9584        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9585    });
 9586
 9587    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9588
 9589    cx.set_state(indoc! { r#"fn a() {
 9590          // what
 9591          // a
 9592          // ˇlong
 9593          // method
 9594          // I
 9595          // sure
 9596          // hope
 9597          // it
 9598          // works
 9599    }"# });
 9600
 9601    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9602    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9603    cx.update(|_, cx| {
 9604        multi_buffer.update(cx, |multi_buffer, cx| {
 9605            multi_buffer.set_excerpts_for_path(
 9606                PathKey::for_buffer(&buffer, cx),
 9607                buffer,
 9608                [Point::new(1, 0)..Point::new(1, 0)],
 9609                3,
 9610                cx,
 9611            );
 9612        });
 9613    });
 9614
 9615    let editor2 = cx.new_window_entity(|window, cx| {
 9616        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9617    });
 9618
 9619    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9620    cx.update_editor(|editor, window, cx| {
 9621        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9622            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9623        })
 9624    });
 9625
 9626    cx.assert_editor_state(indoc! { "
 9627        fn a() {
 9628              // what
 9629              // a
 9630        ˇ      // long
 9631              // method"});
 9632
 9633    cx.update_editor(|editor, window, cx| {
 9634        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9635    });
 9636
 9637    // Although we could potentially make the action work when the syntax node
 9638    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9639    // did. Maybe we could also expand the excerpt to contain the range?
 9640    cx.assert_editor_state(indoc! { "
 9641        fn a() {
 9642              // what
 9643              // a
 9644        ˇ      // long
 9645              // method"});
 9646}
 9647
 9648#[gpui::test]
 9649async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9650    init_test(cx, |_| {});
 9651
 9652    let base_text = r#"
 9653        impl A {
 9654            // this is an uncommitted comment
 9655
 9656            fn b() {
 9657                c();
 9658            }
 9659
 9660            // this is another uncommitted comment
 9661
 9662            fn d() {
 9663                // e
 9664                // f
 9665            }
 9666        }
 9667
 9668        fn g() {
 9669            // h
 9670        }
 9671    "#
 9672    .unindent();
 9673
 9674    let text = r#"
 9675        ˇimpl A {
 9676
 9677            fn b() {
 9678                c();
 9679            }
 9680
 9681            fn d() {
 9682                // e
 9683                // f
 9684            }
 9685        }
 9686
 9687        fn g() {
 9688            // h
 9689        }
 9690    "#
 9691    .unindent();
 9692
 9693    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9694    cx.set_state(&text);
 9695    cx.set_head_text(&base_text);
 9696    cx.update_editor(|editor, window, cx| {
 9697        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9698    });
 9699
 9700    cx.assert_state_with_diff(
 9701        "
 9702        ˇimpl A {
 9703      -     // this is an uncommitted comment
 9704
 9705            fn b() {
 9706                c();
 9707            }
 9708
 9709      -     // this is another uncommitted comment
 9710      -
 9711            fn d() {
 9712                // e
 9713                // f
 9714            }
 9715        }
 9716
 9717        fn g() {
 9718            // h
 9719        }
 9720    "
 9721        .unindent(),
 9722    );
 9723
 9724    let expected_display_text = "
 9725        impl A {
 9726            // this is an uncommitted comment
 9727
 9728            fn b() {
 9729 9730            }
 9731
 9732            // this is another uncommitted comment
 9733
 9734            fn d() {
 9735 9736            }
 9737        }
 9738
 9739        fn g() {
 9740 9741        }
 9742        "
 9743    .unindent();
 9744
 9745    cx.update_editor(|editor, window, cx| {
 9746        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9747        assert_eq!(editor.display_text(cx), expected_display_text);
 9748    });
 9749}
 9750
 9751#[gpui::test]
 9752async fn test_autoindent(cx: &mut TestAppContext) {
 9753    init_test(cx, |_| {});
 9754
 9755    let language = Arc::new(
 9756        Language::new(
 9757            LanguageConfig {
 9758                brackets: BracketPairConfig {
 9759                    pairs: vec![
 9760                        BracketPair {
 9761                            start: "{".to_string(),
 9762                            end: "}".to_string(),
 9763                            close: false,
 9764                            surround: false,
 9765                            newline: true,
 9766                        },
 9767                        BracketPair {
 9768                            start: "(".to_string(),
 9769                            end: ")".to_string(),
 9770                            close: false,
 9771                            surround: false,
 9772                            newline: true,
 9773                        },
 9774                    ],
 9775                    ..Default::default()
 9776                },
 9777                ..Default::default()
 9778            },
 9779            Some(tree_sitter_rust::LANGUAGE.into()),
 9780        )
 9781        .with_indents_query(
 9782            r#"
 9783                (_ "(" ")" @end) @indent
 9784                (_ "{" "}" @end) @indent
 9785            "#,
 9786        )
 9787        .unwrap(),
 9788    );
 9789
 9790    let text = "fn a() {}";
 9791
 9792    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9793    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9794    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9795    editor
 9796        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9797        .await;
 9798
 9799    editor.update_in(cx, |editor, window, cx| {
 9800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9801            s.select_ranges([
 9802                MultiBufferOffset(5)..MultiBufferOffset(5),
 9803                MultiBufferOffset(8)..MultiBufferOffset(8),
 9804                MultiBufferOffset(9)..MultiBufferOffset(9),
 9805            ])
 9806        });
 9807        editor.newline(&Newline, window, cx);
 9808        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9809        assert_eq!(
 9810            editor.selections.ranges(&editor.display_snapshot(cx)),
 9811            &[
 9812                Point::new(1, 4)..Point::new(1, 4),
 9813                Point::new(3, 4)..Point::new(3, 4),
 9814                Point::new(5, 0)..Point::new(5, 0)
 9815            ]
 9816        );
 9817    });
 9818}
 9819
 9820#[gpui::test]
 9821async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9822    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9823
 9824    let language = Arc::new(
 9825        Language::new(
 9826            LanguageConfig {
 9827                brackets: BracketPairConfig {
 9828                    pairs: vec![
 9829                        BracketPair {
 9830                            start: "{".to_string(),
 9831                            end: "}".to_string(),
 9832                            close: false,
 9833                            surround: false,
 9834                            newline: true,
 9835                        },
 9836                        BracketPair {
 9837                            start: "(".to_string(),
 9838                            end: ")".to_string(),
 9839                            close: false,
 9840                            surround: false,
 9841                            newline: true,
 9842                        },
 9843                    ],
 9844                    ..Default::default()
 9845                },
 9846                ..Default::default()
 9847            },
 9848            Some(tree_sitter_rust::LANGUAGE.into()),
 9849        )
 9850        .with_indents_query(
 9851            r#"
 9852                (_ "(" ")" @end) @indent
 9853                (_ "{" "}" @end) @indent
 9854            "#,
 9855        )
 9856        .unwrap(),
 9857    );
 9858
 9859    let text = "fn a() {}";
 9860
 9861    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
 9862    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9863    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9864    editor
 9865        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9866        .await;
 9867
 9868    editor.update_in(cx, |editor, window, cx| {
 9869        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9870            s.select_ranges([
 9871                MultiBufferOffset(5)..MultiBufferOffset(5),
 9872                MultiBufferOffset(8)..MultiBufferOffset(8),
 9873                MultiBufferOffset(9)..MultiBufferOffset(9),
 9874            ])
 9875        });
 9876        editor.newline(&Newline, window, cx);
 9877        assert_eq!(
 9878            editor.text(cx),
 9879            indoc!(
 9880                "
 9881                fn a(
 9882
 9883                ) {
 9884
 9885                }
 9886                "
 9887            )
 9888        );
 9889        assert_eq!(
 9890            editor.selections.ranges(&editor.display_snapshot(cx)),
 9891            &[
 9892                Point::new(1, 0)..Point::new(1, 0),
 9893                Point::new(3, 0)..Point::new(3, 0),
 9894                Point::new(5, 0)..Point::new(5, 0)
 9895            ]
 9896        );
 9897    });
 9898}
 9899
 9900#[gpui::test]
 9901async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9902    init_test(cx, |settings| {
 9903        settings.defaults.auto_indent = Some(true);
 9904        settings.languages.0.insert(
 9905            "python".into(),
 9906            LanguageSettingsContent {
 9907                auto_indent: Some(false),
 9908                ..Default::default()
 9909            },
 9910        );
 9911    });
 9912
 9913    let mut cx = EditorTestContext::new(cx).await;
 9914
 9915    let injected_language = Arc::new(
 9916        Language::new(
 9917            LanguageConfig {
 9918                brackets: BracketPairConfig {
 9919                    pairs: vec![
 9920                        BracketPair {
 9921                            start: "{".to_string(),
 9922                            end: "}".to_string(),
 9923                            close: false,
 9924                            surround: false,
 9925                            newline: true,
 9926                        },
 9927                        BracketPair {
 9928                            start: "(".to_string(),
 9929                            end: ")".to_string(),
 9930                            close: true,
 9931                            surround: false,
 9932                            newline: true,
 9933                        },
 9934                    ],
 9935                    ..Default::default()
 9936                },
 9937                name: "python".into(),
 9938                ..Default::default()
 9939            },
 9940            Some(tree_sitter_python::LANGUAGE.into()),
 9941        )
 9942        .with_indents_query(
 9943            r#"
 9944                (_ "(" ")" @end) @indent
 9945                (_ "{" "}" @end) @indent
 9946            "#,
 9947        )
 9948        .unwrap(),
 9949    );
 9950
 9951    let language = Arc::new(
 9952        Language::new(
 9953            LanguageConfig {
 9954                brackets: BracketPairConfig {
 9955                    pairs: vec![
 9956                        BracketPair {
 9957                            start: "{".to_string(),
 9958                            end: "}".to_string(),
 9959                            close: false,
 9960                            surround: false,
 9961                            newline: true,
 9962                        },
 9963                        BracketPair {
 9964                            start: "(".to_string(),
 9965                            end: ")".to_string(),
 9966                            close: true,
 9967                            surround: false,
 9968                            newline: true,
 9969                        },
 9970                    ],
 9971                    ..Default::default()
 9972                },
 9973                name: LanguageName::new_static("rust"),
 9974                ..Default::default()
 9975            },
 9976            Some(tree_sitter_rust::LANGUAGE.into()),
 9977        )
 9978        .with_indents_query(
 9979            r#"
 9980                (_ "(" ")" @end) @indent
 9981                (_ "{" "}" @end) @indent
 9982            "#,
 9983        )
 9984        .unwrap()
 9985        .with_injection_query(
 9986            r#"
 9987            (macro_invocation
 9988                macro: (identifier) @_macro_name
 9989                (token_tree) @injection.content
 9990                (#set! injection.language "python"))
 9991           "#,
 9992        )
 9993        .unwrap(),
 9994    );
 9995
 9996    cx.language_registry().add(injected_language);
 9997    cx.language_registry().add(language.clone());
 9998
 9999    cx.update_buffer(|buffer, cx| {
10000        buffer.set_language(Some(language), cx);
10001    });
10002
10003    cx.set_state(r#"struct A {ˇ}"#);
10004
10005    cx.update_editor(|editor, window, cx| {
10006        editor.newline(&Default::default(), window, cx);
10007    });
10008
10009    cx.assert_editor_state(indoc!(
10010        "struct A {
10011            ˇ
10012        }"
10013    ));
10014
10015    cx.set_state(r#"select_biased!(ˇ)"#);
10016
10017    cx.update_editor(|editor, window, cx| {
10018        editor.newline(&Default::default(), window, cx);
10019        editor.handle_input("def ", window, cx);
10020        editor.handle_input("(", window, cx);
10021        editor.newline(&Default::default(), window, cx);
10022        editor.handle_input("a", window, cx);
10023    });
10024
10025    cx.assert_editor_state(indoc!(
10026        "select_biased!(
10027        def (
1002810029        )
10030        )"
10031    ));
10032}
10033
10034#[gpui::test]
10035async fn test_autoindent_selections(cx: &mut TestAppContext) {
10036    init_test(cx, |_| {});
10037
10038    {
10039        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10040        cx.set_state(indoc! {"
10041            impl A {
10042
10043                fn b() {}
10044
10045            «fn c() {
10046
10047            }ˇ»
10048            }
10049        "});
10050
10051        cx.update_editor(|editor, window, cx| {
10052            editor.autoindent(&Default::default(), window, cx);
10053        });
10054
10055        cx.assert_editor_state(indoc! {"
10056            impl A {
10057
10058                fn b() {}
10059
10060                «fn c() {
10061
10062                }ˇ»
10063            }
10064        "});
10065    }
10066
10067    {
10068        let mut cx = EditorTestContext::new_multibuffer(
10069            cx,
10070            [indoc! { "
10071                impl A {
10072                «
10073                // a
10074                fn b(){}
10075                »
10076                «
10077                    }
10078                    fn c(){}
10079                »
10080            "}],
10081        );
10082
10083        let buffer = cx.update_editor(|editor, _, cx| {
10084            let buffer = editor.buffer().update(cx, |buffer, _| {
10085                buffer.all_buffers().iter().next().unwrap().clone()
10086            });
10087            buffer.update(cx, |buffer, cx| buffer.set_language(Some(rust_lang()), cx));
10088            buffer
10089        });
10090
10091        cx.run_until_parked();
10092        cx.update_editor(|editor, window, cx| {
10093            editor.select_all(&Default::default(), window, cx);
10094            editor.autoindent(&Default::default(), window, cx)
10095        });
10096        cx.run_until_parked();
10097
10098        cx.update(|_, cx| {
10099            assert_eq!(
10100                buffer.read(cx).text(),
10101                indoc! { "
10102                    impl A {
10103
10104                        // a
10105                        fn b(){}
10106
10107
10108                    }
10109                    fn c(){}
10110
10111                " }
10112            )
10113        });
10114    }
10115}
10116
10117#[gpui::test]
10118async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10119    init_test(cx, |_| {});
10120
10121    let mut cx = EditorTestContext::new(cx).await;
10122
10123    let language = Arc::new(Language::new(
10124        LanguageConfig {
10125            brackets: BracketPairConfig {
10126                pairs: vec![
10127                    BracketPair {
10128                        start: "{".to_string(),
10129                        end: "}".to_string(),
10130                        close: true,
10131                        surround: true,
10132                        newline: true,
10133                    },
10134                    BracketPair {
10135                        start: "(".to_string(),
10136                        end: ")".to_string(),
10137                        close: true,
10138                        surround: true,
10139                        newline: true,
10140                    },
10141                    BracketPair {
10142                        start: "/*".to_string(),
10143                        end: " */".to_string(),
10144                        close: true,
10145                        surround: true,
10146                        newline: true,
10147                    },
10148                    BracketPair {
10149                        start: "[".to_string(),
10150                        end: "]".to_string(),
10151                        close: false,
10152                        surround: false,
10153                        newline: true,
10154                    },
10155                    BracketPair {
10156                        start: "\"".to_string(),
10157                        end: "\"".to_string(),
10158                        close: true,
10159                        surround: true,
10160                        newline: false,
10161                    },
10162                    BracketPair {
10163                        start: "<".to_string(),
10164                        end: ">".to_string(),
10165                        close: false,
10166                        surround: true,
10167                        newline: true,
10168                    },
10169                ],
10170                ..Default::default()
10171            },
10172            autoclose_before: "})]".to_string(),
10173            ..Default::default()
10174        },
10175        Some(tree_sitter_rust::LANGUAGE.into()),
10176    ));
10177
10178    cx.language_registry().add(language.clone());
10179    cx.update_buffer(|buffer, cx| {
10180        buffer.set_language(Some(language), cx);
10181    });
10182
10183    cx.set_state(
10184        &r#"
10185            🏀ˇ
10186            εˇ
10187            ❤️ˇ
10188        "#
10189        .unindent(),
10190    );
10191
10192    // autoclose multiple nested brackets at multiple cursors
10193    cx.update_editor(|editor, window, cx| {
10194        editor.handle_input("{", window, cx);
10195        editor.handle_input("{", window, cx);
10196        editor.handle_input("{", window, cx);
10197    });
10198    cx.assert_editor_state(
10199        &"
10200            🏀{{{ˇ}}}
10201            ε{{{ˇ}}}
10202            ❤️{{{ˇ}}}
10203        "
10204        .unindent(),
10205    );
10206
10207    // insert a different closing bracket
10208    cx.update_editor(|editor, window, cx| {
10209        editor.handle_input(")", window, cx);
10210    });
10211    cx.assert_editor_state(
10212        &"
10213            🏀{{{)ˇ}}}
10214            ε{{{)ˇ}}}
10215            ❤️{{{)ˇ}}}
10216        "
10217        .unindent(),
10218    );
10219
10220    // skip over the auto-closed brackets when typing a closing bracket
10221    cx.update_editor(|editor, window, cx| {
10222        editor.move_right(&MoveRight, window, cx);
10223        editor.handle_input("}", window, cx);
10224        editor.handle_input("}", window, cx);
10225        editor.handle_input("}", window, cx);
10226    });
10227    cx.assert_editor_state(
10228        &"
10229            🏀{{{)}}}}ˇ
10230            ε{{{)}}}}ˇ
10231            ❤️{{{)}}}}ˇ
10232        "
10233        .unindent(),
10234    );
10235
10236    // autoclose multi-character pairs
10237    cx.set_state(
10238        &"
10239            ˇ
10240            ˇ
10241        "
10242        .unindent(),
10243    );
10244    cx.update_editor(|editor, window, cx| {
10245        editor.handle_input("/", window, cx);
10246        editor.handle_input("*", window, cx);
10247    });
10248    cx.assert_editor_state(
10249        &"
10250            /*ˇ */
10251            /*ˇ */
10252        "
10253        .unindent(),
10254    );
10255
10256    // one cursor autocloses a multi-character pair, one cursor
10257    // does not autoclose.
10258    cx.set_state(
10259        &"
1026010261            ˇ
10262        "
10263        .unindent(),
10264    );
10265    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10266    cx.assert_editor_state(
10267        &"
10268            /*ˇ */
1026910270        "
10271        .unindent(),
10272    );
10273
10274    // Don't autoclose if the next character isn't whitespace and isn't
10275    // listed in the language's "autoclose_before" section.
10276    cx.set_state("ˇa b");
10277    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10278    cx.assert_editor_state("{ˇa b");
10279
10280    // Don't autoclose if `close` is false for the bracket pair
10281    cx.set_state("ˇ");
10282    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10283    cx.assert_editor_state("");
10284
10285    // Surround with brackets if text is selected
10286    cx.set_state("«aˇ» b");
10287    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10288    cx.assert_editor_state("{«aˇ»} b");
10289
10290    // Autoclose when not immediately after a word character
10291    cx.set_state("a ˇ");
10292    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10293    cx.assert_editor_state("a \"ˇ\"");
10294
10295    // Autoclose pair where the start and end characters are the same
10296    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10297    cx.assert_editor_state("a \"\"ˇ");
10298
10299    // Don't autoclose when immediately after a word character
10300    cx.set_state("");
10301    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10302    cx.assert_editor_state("a\"ˇ");
10303
10304    // Do autoclose when after a non-word character
10305    cx.set_state("");
10306    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10307    cx.assert_editor_state("{\"ˇ\"");
10308
10309    // Non identical pairs autoclose regardless of preceding character
10310    cx.set_state("");
10311    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10312    cx.assert_editor_state("a{ˇ}");
10313
10314    // Don't autoclose pair if autoclose is disabled
10315    cx.set_state("ˇ");
10316    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10317    cx.assert_editor_state("");
10318
10319    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10320    cx.set_state("«aˇ» b");
10321    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10322    cx.assert_editor_state("<«aˇ»> b");
10323}
10324
10325#[gpui::test]
10326async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10327    init_test(cx, |settings| {
10328        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10329    });
10330
10331    let mut cx = EditorTestContext::new(cx).await;
10332
10333    let language = Arc::new(Language::new(
10334        LanguageConfig {
10335            brackets: BracketPairConfig {
10336                pairs: vec![
10337                    BracketPair {
10338                        start: "{".to_string(),
10339                        end: "}".to_string(),
10340                        close: true,
10341                        surround: true,
10342                        newline: true,
10343                    },
10344                    BracketPair {
10345                        start: "(".to_string(),
10346                        end: ")".to_string(),
10347                        close: true,
10348                        surround: true,
10349                        newline: true,
10350                    },
10351                    BracketPair {
10352                        start: "[".to_string(),
10353                        end: "]".to_string(),
10354                        close: false,
10355                        surround: false,
10356                        newline: true,
10357                    },
10358                ],
10359                ..Default::default()
10360            },
10361            autoclose_before: "})]".to_string(),
10362            ..Default::default()
10363        },
10364        Some(tree_sitter_rust::LANGUAGE.into()),
10365    ));
10366
10367    cx.language_registry().add(language.clone());
10368    cx.update_buffer(|buffer, cx| {
10369        buffer.set_language(Some(language), cx);
10370    });
10371
10372    cx.set_state(
10373        &"
10374            ˇ
10375            ˇ
10376            ˇ
10377        "
10378        .unindent(),
10379    );
10380
10381    // ensure only matching closing brackets are skipped over
10382    cx.update_editor(|editor, window, cx| {
10383        editor.handle_input("}", window, cx);
10384        editor.move_left(&MoveLeft, window, cx);
10385        editor.handle_input(")", window, cx);
10386        editor.move_left(&MoveLeft, window, cx);
10387    });
10388    cx.assert_editor_state(
10389        &"
10390            ˇ)}
10391            ˇ)}
10392            ˇ)}
10393        "
10394        .unindent(),
10395    );
10396
10397    // skip-over closing brackets at multiple cursors
10398    cx.update_editor(|editor, window, cx| {
10399        editor.handle_input(")", window, cx);
10400        editor.handle_input("}", window, cx);
10401    });
10402    cx.assert_editor_state(
10403        &"
10404            )}ˇ
10405            )}ˇ
10406            )}ˇ
10407        "
10408        .unindent(),
10409    );
10410
10411    // ignore non-close brackets
10412    cx.update_editor(|editor, window, cx| {
10413        editor.handle_input("]", window, cx);
10414        editor.move_left(&MoveLeft, window, cx);
10415        editor.handle_input("]", window, cx);
10416    });
10417    cx.assert_editor_state(
10418        &"
10419            )}]ˇ]
10420            )}]ˇ]
10421            )}]ˇ]
10422        "
10423        .unindent(),
10424    );
10425}
10426
10427#[gpui::test]
10428async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10429    init_test(cx, |_| {});
10430
10431    let mut cx = EditorTestContext::new(cx).await;
10432
10433    let html_language = Arc::new(
10434        Language::new(
10435            LanguageConfig {
10436                name: "HTML".into(),
10437                brackets: BracketPairConfig {
10438                    pairs: vec![
10439                        BracketPair {
10440                            start: "<".into(),
10441                            end: ">".into(),
10442                            close: true,
10443                            ..Default::default()
10444                        },
10445                        BracketPair {
10446                            start: "{".into(),
10447                            end: "}".into(),
10448                            close: true,
10449                            ..Default::default()
10450                        },
10451                        BracketPair {
10452                            start: "(".into(),
10453                            end: ")".into(),
10454                            close: true,
10455                            ..Default::default()
10456                        },
10457                    ],
10458                    ..Default::default()
10459                },
10460                autoclose_before: "})]>".into(),
10461                ..Default::default()
10462            },
10463            Some(tree_sitter_html::LANGUAGE.into()),
10464        )
10465        .with_injection_query(
10466            r#"
10467            (script_element
10468                (raw_text) @injection.content
10469                (#set! injection.language "javascript"))
10470            "#,
10471        )
10472        .unwrap(),
10473    );
10474
10475    let javascript_language = Arc::new(Language::new(
10476        LanguageConfig {
10477            name: "JavaScript".into(),
10478            brackets: BracketPairConfig {
10479                pairs: vec![
10480                    BracketPair {
10481                        start: "/*".into(),
10482                        end: " */".into(),
10483                        close: true,
10484                        ..Default::default()
10485                    },
10486                    BracketPair {
10487                        start: "{".into(),
10488                        end: "}".into(),
10489                        close: true,
10490                        ..Default::default()
10491                    },
10492                    BracketPair {
10493                        start: "(".into(),
10494                        end: ")".into(),
10495                        close: true,
10496                        ..Default::default()
10497                    },
10498                ],
10499                ..Default::default()
10500            },
10501            autoclose_before: "})]>".into(),
10502            ..Default::default()
10503        },
10504        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10505    ));
10506
10507    cx.language_registry().add(html_language.clone());
10508    cx.language_registry().add(javascript_language);
10509    cx.executor().run_until_parked();
10510
10511    cx.update_buffer(|buffer, cx| {
10512        buffer.set_language(Some(html_language), cx);
10513    });
10514
10515    cx.set_state(
10516        &r#"
10517            <body>ˇ
10518                <script>
10519                    var x = 1;ˇ
10520                </script>
10521            </body>ˇ
10522        "#
10523        .unindent(),
10524    );
10525
10526    // Precondition: different languages are active at different locations.
10527    cx.update_editor(|editor, window, cx| {
10528        let snapshot = editor.snapshot(window, cx);
10529        let cursors = editor
10530            .selections
10531            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10532        let languages = cursors
10533            .iter()
10534            .map(|c| snapshot.language_at(c.start).unwrap().name())
10535            .collect::<Vec<_>>();
10536        assert_eq!(
10537            languages,
10538            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10539        );
10540    });
10541
10542    // Angle brackets autoclose in HTML, but not JavaScript.
10543    cx.update_editor(|editor, window, cx| {
10544        editor.handle_input("<", window, cx);
10545        editor.handle_input("a", window, cx);
10546    });
10547    cx.assert_editor_state(
10548        &r#"
10549            <body><aˇ>
10550                <script>
10551                    var x = 1;<aˇ
10552                </script>
10553            </body><aˇ>
10554        "#
10555        .unindent(),
10556    );
10557
10558    // Curly braces and parens autoclose in both HTML and JavaScript.
10559    cx.update_editor(|editor, window, cx| {
10560        editor.handle_input(" b=", window, cx);
10561        editor.handle_input("{", window, cx);
10562        editor.handle_input("c", window, cx);
10563        editor.handle_input("(", window, cx);
10564    });
10565    cx.assert_editor_state(
10566        &r#"
10567            <body><a b={c(ˇ)}>
10568                <script>
10569                    var x = 1;<a b={c(ˇ)}
10570                </script>
10571            </body><a b={c(ˇ)}>
10572        "#
10573        .unindent(),
10574    );
10575
10576    // Brackets that were already autoclosed are skipped.
10577    cx.update_editor(|editor, window, cx| {
10578        editor.handle_input(")", window, cx);
10579        editor.handle_input("d", window, cx);
10580        editor.handle_input("}", window, cx);
10581    });
10582    cx.assert_editor_state(
10583        &r#"
10584            <body><a b={c()d}ˇ>
10585                <script>
10586                    var x = 1;<a b={c()d}ˇ
10587                </script>
10588            </body><a b={c()d}ˇ>
10589        "#
10590        .unindent(),
10591    );
10592    cx.update_editor(|editor, window, cx| {
10593        editor.handle_input(">", window, cx);
10594    });
10595    cx.assert_editor_state(
10596        &r#"
10597            <body><a b={c()d}>ˇ
10598                <script>
10599                    var x = 1;<a b={c()d}>ˇ
10600                </script>
10601            </body><a b={c()d}>ˇ
10602        "#
10603        .unindent(),
10604    );
10605
10606    // Reset
10607    cx.set_state(
10608        &r#"
10609            <body>ˇ
10610                <script>
10611                    var x = 1;ˇ
10612                </script>
10613            </body>ˇ
10614        "#
10615        .unindent(),
10616    );
10617
10618    cx.update_editor(|editor, window, cx| {
10619        editor.handle_input("<", window, cx);
10620    });
10621    cx.assert_editor_state(
10622        &r#"
10623            <body><ˇ>
10624                <script>
10625                    var x = 1;<ˇ
10626                </script>
10627            </body><ˇ>
10628        "#
10629        .unindent(),
10630    );
10631
10632    // When backspacing, the closing angle brackets are removed.
10633    cx.update_editor(|editor, window, cx| {
10634        editor.backspace(&Backspace, window, cx);
10635    });
10636    cx.assert_editor_state(
10637        &r#"
10638            <body>ˇ
10639                <script>
10640                    var x = 1;ˇ
10641                </script>
10642            </body>ˇ
10643        "#
10644        .unindent(),
10645    );
10646
10647    // Block comments autoclose in JavaScript, but not HTML.
10648    cx.update_editor(|editor, window, cx| {
10649        editor.handle_input("/", window, cx);
10650        editor.handle_input("*", window, cx);
10651    });
10652    cx.assert_editor_state(
10653        &r#"
10654            <body>/*ˇ
10655                <script>
10656                    var x = 1;/*ˇ */
10657                </script>
10658            </body>/*ˇ
10659        "#
10660        .unindent(),
10661    );
10662}
10663
10664#[gpui::test]
10665async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10666    init_test(cx, |_| {});
10667
10668    let mut cx = EditorTestContext::new(cx).await;
10669
10670    let rust_language = Arc::new(
10671        Language::new(
10672            LanguageConfig {
10673                name: "Rust".into(),
10674                brackets: serde_json::from_value(json!([
10675                    { "start": "{", "end": "}", "close": true, "newline": true },
10676                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10677                ]))
10678                .unwrap(),
10679                autoclose_before: "})]>".into(),
10680                ..Default::default()
10681            },
10682            Some(tree_sitter_rust::LANGUAGE.into()),
10683        )
10684        .with_override_query("(string_literal) @string")
10685        .unwrap(),
10686    );
10687
10688    cx.language_registry().add(rust_language.clone());
10689    cx.update_buffer(|buffer, cx| {
10690        buffer.set_language(Some(rust_language), cx);
10691    });
10692
10693    cx.set_state(
10694        &r#"
10695            let x = ˇ
10696        "#
10697        .unindent(),
10698    );
10699
10700    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10701    cx.update_editor(|editor, window, cx| {
10702        editor.handle_input("\"", window, cx);
10703    });
10704    cx.assert_editor_state(
10705        &r#"
10706            let x = "ˇ"
10707        "#
10708        .unindent(),
10709    );
10710
10711    // Inserting another quotation mark. The cursor moves across the existing
10712    // automatically-inserted quotation mark.
10713    cx.update_editor(|editor, window, cx| {
10714        editor.handle_input("\"", window, cx);
10715    });
10716    cx.assert_editor_state(
10717        &r#"
10718            let x = ""ˇ
10719        "#
10720        .unindent(),
10721    );
10722
10723    // Reset
10724    cx.set_state(
10725        &r#"
10726            let x = ˇ
10727        "#
10728        .unindent(),
10729    );
10730
10731    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10732    cx.update_editor(|editor, window, cx| {
10733        editor.handle_input("\"", window, cx);
10734        editor.handle_input(" ", window, cx);
10735        editor.move_left(&Default::default(), window, cx);
10736        editor.handle_input("\\", window, cx);
10737        editor.handle_input("\"", window, cx);
10738    });
10739    cx.assert_editor_state(
10740        &r#"
10741            let x = "\"ˇ "
10742        "#
10743        .unindent(),
10744    );
10745
10746    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10747    // mark. Nothing is inserted.
10748    cx.update_editor(|editor, window, cx| {
10749        editor.move_right(&Default::default(), window, cx);
10750        editor.handle_input("\"", window, cx);
10751    });
10752    cx.assert_editor_state(
10753        &r#"
10754            let x = "\" "ˇ
10755        "#
10756        .unindent(),
10757    );
10758}
10759
10760#[gpui::test]
10761async fn test_surround_with_pair(cx: &mut TestAppContext) {
10762    init_test(cx, |_| {});
10763
10764    let language = Arc::new(Language::new(
10765        LanguageConfig {
10766            brackets: BracketPairConfig {
10767                pairs: vec![
10768                    BracketPair {
10769                        start: "{".to_string(),
10770                        end: "}".to_string(),
10771                        close: true,
10772                        surround: true,
10773                        newline: true,
10774                    },
10775                    BracketPair {
10776                        start: "/* ".to_string(),
10777                        end: "*/".to_string(),
10778                        close: true,
10779                        surround: true,
10780                        ..Default::default()
10781                    },
10782                ],
10783                ..Default::default()
10784            },
10785            ..Default::default()
10786        },
10787        Some(tree_sitter_rust::LANGUAGE.into()),
10788    ));
10789
10790    let text = r#"
10791        a
10792        b
10793        c
10794    "#
10795    .unindent();
10796
10797    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10798    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10799    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10800    editor
10801        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10802        .await;
10803
10804    editor.update_in(cx, |editor, window, cx| {
10805        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10806            s.select_display_ranges([
10807                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10808                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10809                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10810            ])
10811        });
10812
10813        editor.handle_input("{", window, cx);
10814        editor.handle_input("{", window, cx);
10815        editor.handle_input("{", window, cx);
10816        assert_eq!(
10817            editor.text(cx),
10818            "
10819                {{{a}}}
10820                {{{b}}}
10821                {{{c}}}
10822            "
10823            .unindent()
10824        );
10825        assert_eq!(
10826            display_ranges(editor, cx),
10827            [
10828                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10829                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10830                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10831            ]
10832        );
10833
10834        editor.undo(&Undo, window, cx);
10835        editor.undo(&Undo, window, cx);
10836        editor.undo(&Undo, window, cx);
10837        assert_eq!(
10838            editor.text(cx),
10839            "
10840                a
10841                b
10842                c
10843            "
10844            .unindent()
10845        );
10846        assert_eq!(
10847            display_ranges(editor, cx),
10848            [
10849                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10850                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10851                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10852            ]
10853        );
10854
10855        // Ensure inserting the first character of a multi-byte bracket pair
10856        // doesn't surround the selections with the bracket.
10857        editor.handle_input("/", window, cx);
10858        assert_eq!(
10859            editor.text(cx),
10860            "
10861                /
10862                /
10863                /
10864            "
10865            .unindent()
10866        );
10867        assert_eq!(
10868            display_ranges(editor, cx),
10869            [
10870                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10871                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10872                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10873            ]
10874        );
10875
10876        editor.undo(&Undo, window, cx);
10877        assert_eq!(
10878            editor.text(cx),
10879            "
10880                a
10881                b
10882                c
10883            "
10884            .unindent()
10885        );
10886        assert_eq!(
10887            display_ranges(editor, cx),
10888            [
10889                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10890                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10891                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10892            ]
10893        );
10894
10895        // Ensure inserting the last character of a multi-byte bracket pair
10896        // doesn't surround the selections with the bracket.
10897        editor.handle_input("*", window, cx);
10898        assert_eq!(
10899            editor.text(cx),
10900            "
10901                *
10902                *
10903                *
10904            "
10905            .unindent()
10906        );
10907        assert_eq!(
10908            display_ranges(editor, cx),
10909            [
10910                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10911                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10912                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10913            ]
10914        );
10915    });
10916}
10917
10918#[gpui::test]
10919async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10920    init_test(cx, |_| {});
10921
10922    let language = Arc::new(Language::new(
10923        LanguageConfig {
10924            brackets: BracketPairConfig {
10925                pairs: vec![BracketPair {
10926                    start: "{".to_string(),
10927                    end: "}".to_string(),
10928                    close: true,
10929                    surround: true,
10930                    newline: true,
10931                }],
10932                ..Default::default()
10933            },
10934            autoclose_before: "}".to_string(),
10935            ..Default::default()
10936        },
10937        Some(tree_sitter_rust::LANGUAGE.into()),
10938    ));
10939
10940    let text = r#"
10941        a
10942        b
10943        c
10944    "#
10945    .unindent();
10946
10947    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
10948    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10949    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10950    editor
10951        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10952        .await;
10953
10954    editor.update_in(cx, |editor, window, cx| {
10955        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10956            s.select_ranges([
10957                Point::new(0, 1)..Point::new(0, 1),
10958                Point::new(1, 1)..Point::new(1, 1),
10959                Point::new(2, 1)..Point::new(2, 1),
10960            ])
10961        });
10962
10963        editor.handle_input("{", window, cx);
10964        editor.handle_input("{", window, cx);
10965        editor.handle_input("_", window, cx);
10966        assert_eq!(
10967            editor.text(cx),
10968            "
10969                a{{_}}
10970                b{{_}}
10971                c{{_}}
10972            "
10973            .unindent()
10974        );
10975        assert_eq!(
10976            editor
10977                .selections
10978                .ranges::<Point>(&editor.display_snapshot(cx)),
10979            [
10980                Point::new(0, 4)..Point::new(0, 4),
10981                Point::new(1, 4)..Point::new(1, 4),
10982                Point::new(2, 4)..Point::new(2, 4)
10983            ]
10984        );
10985
10986        editor.backspace(&Default::default(), window, cx);
10987        editor.backspace(&Default::default(), window, cx);
10988        assert_eq!(
10989            editor.text(cx),
10990            "
10991                a{}
10992                b{}
10993                c{}
10994            "
10995            .unindent()
10996        );
10997        assert_eq!(
10998            editor
10999                .selections
11000                .ranges::<Point>(&editor.display_snapshot(cx)),
11001            [
11002                Point::new(0, 2)..Point::new(0, 2),
11003                Point::new(1, 2)..Point::new(1, 2),
11004                Point::new(2, 2)..Point::new(2, 2)
11005            ]
11006        );
11007
11008        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11009        assert_eq!(
11010            editor.text(cx),
11011            "
11012                a
11013                b
11014                c
11015            "
11016            .unindent()
11017        );
11018        assert_eq!(
11019            editor
11020                .selections
11021                .ranges::<Point>(&editor.display_snapshot(cx)),
11022            [
11023                Point::new(0, 1)..Point::new(0, 1),
11024                Point::new(1, 1)..Point::new(1, 1),
11025                Point::new(2, 1)..Point::new(2, 1)
11026            ]
11027        );
11028    });
11029}
11030
11031#[gpui::test]
11032async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11033    init_test(cx, |settings| {
11034        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11035    });
11036
11037    let mut cx = EditorTestContext::new(cx).await;
11038
11039    let language = Arc::new(Language::new(
11040        LanguageConfig {
11041            brackets: BracketPairConfig {
11042                pairs: vec![
11043                    BracketPair {
11044                        start: "{".to_string(),
11045                        end: "}".to_string(),
11046                        close: true,
11047                        surround: true,
11048                        newline: true,
11049                    },
11050                    BracketPair {
11051                        start: "(".to_string(),
11052                        end: ")".to_string(),
11053                        close: true,
11054                        surround: true,
11055                        newline: true,
11056                    },
11057                    BracketPair {
11058                        start: "[".to_string(),
11059                        end: "]".to_string(),
11060                        close: false,
11061                        surround: true,
11062                        newline: true,
11063                    },
11064                ],
11065                ..Default::default()
11066            },
11067            autoclose_before: "})]".to_string(),
11068            ..Default::default()
11069        },
11070        Some(tree_sitter_rust::LANGUAGE.into()),
11071    ));
11072
11073    cx.language_registry().add(language.clone());
11074    cx.update_buffer(|buffer, cx| {
11075        buffer.set_language(Some(language), cx);
11076    });
11077
11078    cx.set_state(
11079        &"
11080            {(ˇ)}
11081            [[ˇ]]
11082            {(ˇ)}
11083        "
11084        .unindent(),
11085    );
11086
11087    cx.update_editor(|editor, window, cx| {
11088        editor.backspace(&Default::default(), window, cx);
11089        editor.backspace(&Default::default(), window, cx);
11090    });
11091
11092    cx.assert_editor_state(
11093        &"
11094            ˇ
11095            ˇ]]
11096            ˇ
11097        "
11098        .unindent(),
11099    );
11100
11101    cx.update_editor(|editor, window, cx| {
11102        editor.handle_input("{", window, cx);
11103        editor.handle_input("{", window, cx);
11104        editor.move_right(&MoveRight, window, cx);
11105        editor.move_right(&MoveRight, window, cx);
11106        editor.move_left(&MoveLeft, window, cx);
11107        editor.move_left(&MoveLeft, window, cx);
11108        editor.backspace(&Default::default(), window, cx);
11109    });
11110
11111    cx.assert_editor_state(
11112        &"
11113            {ˇ}
11114            {ˇ}]]
11115            {ˇ}
11116        "
11117        .unindent(),
11118    );
11119
11120    cx.update_editor(|editor, window, cx| {
11121        editor.backspace(&Default::default(), window, cx);
11122    });
11123
11124    cx.assert_editor_state(
11125        &"
11126            ˇ
11127            ˇ]]
11128            ˇ
11129        "
11130        .unindent(),
11131    );
11132}
11133
11134#[gpui::test]
11135async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11136    init_test(cx, |_| {});
11137
11138    let language = Arc::new(Language::new(
11139        LanguageConfig::default(),
11140        Some(tree_sitter_rust::LANGUAGE.into()),
11141    ));
11142
11143    let buffer = cx.new(|cx| Buffer::local("", cx).with_language(language, cx));
11144    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11145    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11146    editor
11147        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11148        .await;
11149
11150    editor.update_in(cx, |editor, window, cx| {
11151        editor.set_auto_replace_emoji_shortcode(true);
11152
11153        editor.handle_input("Hello ", window, cx);
11154        editor.handle_input(":wave", window, cx);
11155        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11156
11157        editor.handle_input(":", window, cx);
11158        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11159
11160        editor.handle_input(" :smile", window, cx);
11161        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11162
11163        editor.handle_input(":", window, cx);
11164        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11165
11166        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11167        editor.handle_input(":wave", window, cx);
11168        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11169
11170        editor.handle_input(":", window, cx);
11171        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11172
11173        editor.handle_input(":1", window, cx);
11174        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11175
11176        editor.handle_input(":", window, cx);
11177        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11178
11179        // Ensure shortcode does not get replaced when it is part of a word
11180        editor.handle_input(" Test:wave", window, cx);
11181        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11182
11183        editor.handle_input(":", window, cx);
11184        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11185
11186        editor.set_auto_replace_emoji_shortcode(false);
11187
11188        // Ensure shortcode does not get replaced when auto replace is off
11189        editor.handle_input(" :wave", window, cx);
11190        assert_eq!(
11191            editor.text(cx),
11192            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11193        );
11194
11195        editor.handle_input(":", window, cx);
11196        assert_eq!(
11197            editor.text(cx),
11198            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11199        );
11200    });
11201}
11202
11203#[gpui::test]
11204async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11205    init_test(cx, |_| {});
11206
11207    let (text, insertion_ranges) = marked_text_ranges(
11208        indoc! {"
11209            ˇ
11210        "},
11211        false,
11212    );
11213
11214    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11215    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11216
11217    _ = editor.update_in(cx, |editor, window, cx| {
11218        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11219
11220        editor
11221            .insert_snippet(
11222                &insertion_ranges
11223                    .iter()
11224                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11225                    .collect::<Vec<_>>(),
11226                snippet,
11227                window,
11228                cx,
11229            )
11230            .unwrap();
11231
11232        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11233            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11234            assert_eq!(editor.text(cx), expected_text);
11235            assert_eq!(
11236                editor.selections.ranges(&editor.display_snapshot(cx)),
11237                selection_ranges
11238                    .iter()
11239                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11240                    .collect::<Vec<_>>()
11241            );
11242        }
11243
11244        assert(
11245            editor,
11246            cx,
11247            indoc! {"
11248            type «» =•
11249            "},
11250        );
11251
11252        assert!(editor.context_menu_visible(), "There should be a matches");
11253    });
11254}
11255
11256#[gpui::test]
11257async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11258    init_test(cx, |_| {});
11259
11260    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11261        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11262        assert_eq!(editor.text(cx), expected_text);
11263        assert_eq!(
11264            editor.selections.ranges(&editor.display_snapshot(cx)),
11265            selection_ranges
11266                .iter()
11267                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11268                .collect::<Vec<_>>()
11269        );
11270    }
11271
11272    let (text, insertion_ranges) = marked_text_ranges(
11273        indoc! {"
11274            ˇ
11275        "},
11276        false,
11277    );
11278
11279    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11280    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11281
11282    _ = editor.update_in(cx, |editor, window, cx| {
11283        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11284
11285        editor
11286            .insert_snippet(
11287                &insertion_ranges
11288                    .iter()
11289                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11290                    .collect::<Vec<_>>(),
11291                snippet,
11292                window,
11293                cx,
11294            )
11295            .unwrap();
11296
11297        assert_state(
11298            editor,
11299            cx,
11300            indoc! {"
11301            type «» = ;•
11302            "},
11303        );
11304
11305        assert!(
11306            editor.context_menu_visible(),
11307            "Context menu should be visible for placeholder choices"
11308        );
11309
11310        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11311
11312        assert_state(
11313            editor,
11314            cx,
11315            indoc! {"
11316            type  = «»;•
11317            "},
11318        );
11319
11320        assert!(
11321            !editor.context_menu_visible(),
11322            "Context menu should be hidden after moving to next tabstop"
11323        );
11324
11325        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11326
11327        assert_state(
11328            editor,
11329            cx,
11330            indoc! {"
11331            type  = ; ˇ
11332            "},
11333        );
11334
11335        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11336
11337        assert_state(
11338            editor,
11339            cx,
11340            indoc! {"
11341            type  = ; ˇ
11342            "},
11343        );
11344    });
11345
11346    _ = editor.update_in(cx, |editor, window, cx| {
11347        editor.select_all(&SelectAll, window, cx);
11348        editor.backspace(&Backspace, window, cx);
11349
11350        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11351        let insertion_ranges = editor
11352            .selections
11353            .all(&editor.display_snapshot(cx))
11354            .iter()
11355            .map(|s| s.range())
11356            .collect::<Vec<_>>();
11357
11358        editor
11359            .insert_snippet(&insertion_ranges, snippet, window, cx)
11360            .unwrap();
11361
11362        assert_state(editor, cx, "fn «» = value;•");
11363
11364        assert!(
11365            editor.context_menu_visible(),
11366            "Context menu should be visible for placeholder choices"
11367        );
11368
11369        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11370
11371        assert_state(editor, cx, "fn  = «valueˇ»;•");
11372
11373        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11374
11375        assert_state(editor, cx, "fn «» = value;•");
11376
11377        assert!(
11378            editor.context_menu_visible(),
11379            "Context menu should be visible again after returning to first tabstop"
11380        );
11381
11382        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11383
11384        assert_state(editor, cx, "fn «» = value;•");
11385    });
11386}
11387
11388#[gpui::test]
11389async fn test_snippets(cx: &mut TestAppContext) {
11390    init_test(cx, |_| {});
11391
11392    let mut cx = EditorTestContext::new(cx).await;
11393
11394    cx.set_state(indoc! {"
11395        a.ˇ b
11396        a.ˇ b
11397        a.ˇ b
11398    "});
11399
11400    cx.update_editor(|editor, window, cx| {
11401        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11402        let insertion_ranges = editor
11403            .selections
11404            .all(&editor.display_snapshot(cx))
11405            .iter()
11406            .map(|s| s.range())
11407            .collect::<Vec<_>>();
11408        editor
11409            .insert_snippet(&insertion_ranges, snippet, window, cx)
11410            .unwrap();
11411    });
11412
11413    cx.assert_editor_state(indoc! {"
11414        a.f(«oneˇ», two, «threeˇ») b
11415        a.f(«oneˇ», two, «threeˇ») b
11416        a.f(«oneˇ», two, «threeˇ») b
11417    "});
11418
11419    // Can't move earlier than the first tab stop
11420    cx.update_editor(|editor, window, cx| {
11421        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11422    });
11423    cx.assert_editor_state(indoc! {"
11424        a.f(«oneˇ», two, «threeˇ») b
11425        a.f(«oneˇ», two, «threeˇ») b
11426        a.f(«oneˇ», two, «threeˇ») b
11427    "});
11428
11429    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11430    cx.assert_editor_state(indoc! {"
11431        a.f(one, «twoˇ», three) b
11432        a.f(one, «twoˇ», three) b
11433        a.f(one, «twoˇ», three) b
11434    "});
11435
11436    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11437    cx.assert_editor_state(indoc! {"
11438        a.f(«oneˇ», two, «threeˇ») b
11439        a.f(«oneˇ», two, «threeˇ») b
11440        a.f(«oneˇ», two, «threeˇ») b
11441    "});
11442
11443    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11444    cx.assert_editor_state(indoc! {"
11445        a.f(one, «twoˇ», three) b
11446        a.f(one, «twoˇ», three) b
11447        a.f(one, «twoˇ», three) b
11448    "});
11449    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11450    cx.assert_editor_state(indoc! {"
11451        a.f(one, two, three)ˇ b
11452        a.f(one, two, three)ˇ b
11453        a.f(one, two, three)ˇ b
11454    "});
11455
11456    // As soon as the last tab stop is reached, snippet state is gone
11457    cx.update_editor(|editor, window, cx| {
11458        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11459    });
11460    cx.assert_editor_state(indoc! {"
11461        a.f(one, two, three)ˇ b
11462        a.f(one, two, three)ˇ b
11463        a.f(one, two, three)ˇ b
11464    "});
11465}
11466
11467#[gpui::test]
11468async fn test_snippet_indentation(cx: &mut TestAppContext) {
11469    init_test(cx, |_| {});
11470
11471    let mut cx = EditorTestContext::new(cx).await;
11472
11473    cx.update_editor(|editor, window, cx| {
11474        let snippet = Snippet::parse(indoc! {"
11475            /*
11476             * Multiline comment with leading indentation
11477             *
11478             * $1
11479             */
11480            $0"})
11481        .unwrap();
11482        let insertion_ranges = editor
11483            .selections
11484            .all(&editor.display_snapshot(cx))
11485            .iter()
11486            .map(|s| s.range())
11487            .collect::<Vec<_>>();
11488        editor
11489            .insert_snippet(&insertion_ranges, snippet, window, cx)
11490            .unwrap();
11491    });
11492
11493    cx.assert_editor_state(indoc! {"
11494        /*
11495         * Multiline comment with leading indentation
11496         *
11497         * ˇ
11498         */
11499    "});
11500
11501    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11502    cx.assert_editor_state(indoc! {"
11503        /*
11504         * Multiline comment with leading indentation
11505         *
11506         *•
11507         */
11508        ˇ"});
11509}
11510
11511#[gpui::test]
11512async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11513    init_test(cx, |_| {});
11514
11515    let mut cx = EditorTestContext::new(cx).await;
11516    cx.update_editor(|editor, _, cx| {
11517        editor.project().unwrap().update(cx, |project, cx| {
11518            project.snippets().update(cx, |snippets, _cx| {
11519                let snippet = project::snippet_provider::Snippet {
11520                    prefix: vec!["multi word".to_string()],
11521                    body: "this is many words".to_string(),
11522                    description: Some("description".to_string()),
11523                    name: "multi-word snippet test".to_string(),
11524                };
11525                snippets.add_snippet_for_test(
11526                    None,
11527                    PathBuf::from("test_snippets.json"),
11528                    vec![Arc::new(snippet)],
11529                );
11530            });
11531        })
11532    });
11533
11534    for (input_to_simulate, should_match_snippet) in [
11535        ("m", true),
11536        ("m ", true),
11537        ("m w", true),
11538        ("aa m w", true),
11539        ("aa m g", false),
11540    ] {
11541        cx.set_state("ˇ");
11542        cx.simulate_input(input_to_simulate); // fails correctly
11543
11544        cx.update_editor(|editor, _, _| {
11545            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11546            else {
11547                assert!(!should_match_snippet); // no completions! don't even show the menu
11548                return;
11549            };
11550            assert!(context_menu.visible());
11551            let completions = context_menu.completions.borrow();
11552
11553            assert_eq!(!completions.is_empty(), should_match_snippet);
11554        });
11555    }
11556}
11557
11558#[gpui::test]
11559async fn test_document_format_during_save(cx: &mut TestAppContext) {
11560    init_test(cx, |_| {});
11561
11562    let fs = FakeFs::new(cx.executor());
11563    fs.insert_file(path!("/file.rs"), Default::default()).await;
11564
11565    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11566
11567    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11568    language_registry.add(rust_lang());
11569    let mut fake_servers = language_registry.register_fake_lsp(
11570        "Rust",
11571        FakeLspAdapter {
11572            capabilities: lsp::ServerCapabilities {
11573                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11574                ..Default::default()
11575            },
11576            ..Default::default()
11577        },
11578    );
11579
11580    let buffer = project
11581        .update(cx, |project, cx| {
11582            project.open_local_buffer(path!("/file.rs"), cx)
11583        })
11584        .await
11585        .unwrap();
11586
11587    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11588    let (editor, cx) = cx.add_window_view(|window, cx| {
11589        build_editor_with_project(project.clone(), buffer, window, cx)
11590    });
11591    editor.update_in(cx, |editor, window, cx| {
11592        editor.set_text("one\ntwo\nthree\n", window, cx)
11593    });
11594    assert!(cx.read(|cx| editor.is_dirty(cx)));
11595
11596    cx.executor().start_waiting();
11597    let fake_server = fake_servers.next().await.unwrap();
11598
11599    {
11600        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11601            move |params, _| async move {
11602                assert_eq!(
11603                    params.text_document.uri,
11604                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11605                );
11606                assert_eq!(params.options.tab_size, 4);
11607                Ok(Some(vec![lsp::TextEdit::new(
11608                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11609                    ", ".to_string(),
11610                )]))
11611            },
11612        );
11613        let save = editor
11614            .update_in(cx, |editor, window, cx| {
11615                editor.save(
11616                    SaveOptions {
11617                        format: true,
11618                        autosave: false,
11619                    },
11620                    project.clone(),
11621                    window,
11622                    cx,
11623                )
11624            })
11625            .unwrap();
11626        cx.executor().start_waiting();
11627        save.await;
11628
11629        assert_eq!(
11630            editor.update(cx, |editor, cx| editor.text(cx)),
11631            "one, two\nthree\n"
11632        );
11633        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11634    }
11635
11636    {
11637        editor.update_in(cx, |editor, window, cx| {
11638            editor.set_text("one\ntwo\nthree\n", window, cx)
11639        });
11640        assert!(cx.read(|cx| editor.is_dirty(cx)));
11641
11642        // Ensure we can still save even if formatting hangs.
11643        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11644            move |params, _| async move {
11645                assert_eq!(
11646                    params.text_document.uri,
11647                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11648                );
11649                futures::future::pending::<()>().await;
11650                unreachable!()
11651            },
11652        );
11653        let save = editor
11654            .update_in(cx, |editor, window, cx| {
11655                editor.save(
11656                    SaveOptions {
11657                        format: true,
11658                        autosave: false,
11659                    },
11660                    project.clone(),
11661                    window,
11662                    cx,
11663                )
11664            })
11665            .unwrap();
11666        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11667        cx.executor().start_waiting();
11668        save.await;
11669        assert_eq!(
11670            editor.update(cx, |editor, cx| editor.text(cx)),
11671            "one\ntwo\nthree\n"
11672        );
11673    }
11674
11675    // Set rust language override and assert overridden tabsize is sent to language server
11676    update_test_language_settings(cx, |settings| {
11677        settings.languages.0.insert(
11678            "Rust".into(),
11679            LanguageSettingsContent {
11680                tab_size: NonZeroU32::new(8),
11681                ..Default::default()
11682            },
11683        );
11684    });
11685
11686    {
11687        editor.update_in(cx, |editor, window, cx| {
11688            editor.set_text("somehting_new\n", window, cx)
11689        });
11690        assert!(cx.read(|cx| editor.is_dirty(cx)));
11691        let _formatting_request_signal = fake_server
11692            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11693                assert_eq!(
11694                    params.text_document.uri,
11695                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11696                );
11697                assert_eq!(params.options.tab_size, 8);
11698                Ok(Some(vec![]))
11699            });
11700        let save = editor
11701            .update_in(cx, |editor, window, cx| {
11702                editor.save(
11703                    SaveOptions {
11704                        format: true,
11705                        autosave: false,
11706                    },
11707                    project.clone(),
11708                    window,
11709                    cx,
11710                )
11711            })
11712            .unwrap();
11713        cx.executor().start_waiting();
11714        save.await;
11715    }
11716}
11717
11718#[gpui::test]
11719async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11720    init_test(cx, |settings| {
11721        settings.defaults.ensure_final_newline_on_save = Some(false);
11722    });
11723
11724    let fs = FakeFs::new(cx.executor());
11725    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11726
11727    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11728
11729    let buffer = project
11730        .update(cx, |project, cx| {
11731            project.open_local_buffer(path!("/file.txt"), cx)
11732        })
11733        .await
11734        .unwrap();
11735
11736    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11737    let (editor, cx) = cx.add_window_view(|window, cx| {
11738        build_editor_with_project(project.clone(), buffer, window, cx)
11739    });
11740    editor.update_in(cx, |editor, window, cx| {
11741        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11742            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11743        });
11744    });
11745    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11746
11747    editor.update_in(cx, |editor, window, cx| {
11748        editor.handle_input("\n", window, cx)
11749    });
11750    cx.run_until_parked();
11751    save(&editor, &project, cx).await;
11752    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11753
11754    editor.update_in(cx, |editor, window, cx| {
11755        editor.undo(&Default::default(), window, cx);
11756    });
11757    save(&editor, &project, cx).await;
11758    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11759
11760    editor.update_in(cx, |editor, window, cx| {
11761        editor.redo(&Default::default(), window, cx);
11762    });
11763    cx.run_until_parked();
11764    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11765
11766    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11767        let save = editor
11768            .update_in(cx, |editor, window, cx| {
11769                editor.save(
11770                    SaveOptions {
11771                        format: true,
11772                        autosave: false,
11773                    },
11774                    project.clone(),
11775                    window,
11776                    cx,
11777                )
11778            })
11779            .unwrap();
11780        cx.executor().start_waiting();
11781        save.await;
11782        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11783    }
11784}
11785
11786#[gpui::test]
11787async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11788    init_test(cx, |_| {});
11789
11790    let cols = 4;
11791    let rows = 10;
11792    let sample_text_1 = sample_text(rows, cols, 'a');
11793    assert_eq!(
11794        sample_text_1,
11795        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11796    );
11797    let sample_text_2 = sample_text(rows, cols, 'l');
11798    assert_eq!(
11799        sample_text_2,
11800        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11801    );
11802    let sample_text_3 = sample_text(rows, cols, 'v');
11803    assert_eq!(
11804        sample_text_3,
11805        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11806    );
11807
11808    let fs = FakeFs::new(cx.executor());
11809    fs.insert_tree(
11810        path!("/a"),
11811        json!({
11812            "main.rs": sample_text_1,
11813            "other.rs": sample_text_2,
11814            "lib.rs": sample_text_3,
11815        }),
11816    )
11817    .await;
11818
11819    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11820    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11821    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11822
11823    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11824    language_registry.add(rust_lang());
11825    let mut fake_servers = language_registry.register_fake_lsp(
11826        "Rust",
11827        FakeLspAdapter {
11828            capabilities: lsp::ServerCapabilities {
11829                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11830                ..Default::default()
11831            },
11832            ..Default::default()
11833        },
11834    );
11835
11836    let worktree = project.update(cx, |project, cx| {
11837        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11838        assert_eq!(worktrees.len(), 1);
11839        worktrees.pop().unwrap()
11840    });
11841    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11842
11843    let buffer_1 = project
11844        .update(cx, |project, cx| {
11845            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11846        })
11847        .await
11848        .unwrap();
11849    let buffer_2 = project
11850        .update(cx, |project, cx| {
11851            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11852        })
11853        .await
11854        .unwrap();
11855    let buffer_3 = project
11856        .update(cx, |project, cx| {
11857            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11858        })
11859        .await
11860        .unwrap();
11861
11862    let multi_buffer = cx.new(|cx| {
11863        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11864        multi_buffer.push_excerpts(
11865            buffer_1.clone(),
11866            [
11867                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11868                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11869                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11870            ],
11871            cx,
11872        );
11873        multi_buffer.push_excerpts(
11874            buffer_2.clone(),
11875            [
11876                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11877                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11878                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11879            ],
11880            cx,
11881        );
11882        multi_buffer.push_excerpts(
11883            buffer_3.clone(),
11884            [
11885                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11886                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11887                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11888            ],
11889            cx,
11890        );
11891        multi_buffer
11892    });
11893    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11894        Editor::new(
11895            EditorMode::full(),
11896            multi_buffer,
11897            Some(project.clone()),
11898            window,
11899            cx,
11900        )
11901    });
11902
11903    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11904        editor.change_selections(
11905            SelectionEffects::scroll(Autoscroll::Next),
11906            window,
11907            cx,
11908            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
11909        );
11910        editor.insert("|one|two|three|", window, cx);
11911    });
11912    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11913    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11914        editor.change_selections(
11915            SelectionEffects::scroll(Autoscroll::Next),
11916            window,
11917            cx,
11918            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
11919        );
11920        editor.insert("|four|five|six|", window, cx);
11921    });
11922    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11923
11924    // First two buffers should be edited, but not the third one.
11925    assert_eq!(
11926        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11927        "a|one|two|three|aa\nbbbb\ncccc\n\nffff\ngggg\n\njjjj\nllll\nmmmm\nnnnn|four|five|six|\nr\n\nuuuu\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}",
11928    );
11929    buffer_1.update(cx, |buffer, _| {
11930        assert!(buffer.is_dirty());
11931        assert_eq!(
11932            buffer.text(),
11933            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11934        )
11935    });
11936    buffer_2.update(cx, |buffer, _| {
11937        assert!(buffer.is_dirty());
11938        assert_eq!(
11939            buffer.text(),
11940            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11941        )
11942    });
11943    buffer_3.update(cx, |buffer, _| {
11944        assert!(!buffer.is_dirty());
11945        assert_eq!(buffer.text(), sample_text_3,)
11946    });
11947    cx.executor().run_until_parked();
11948
11949    cx.executor().start_waiting();
11950    let save = multi_buffer_editor
11951        .update_in(cx, |editor, window, cx| {
11952            editor.save(
11953                SaveOptions {
11954                    format: true,
11955                    autosave: false,
11956                },
11957                project.clone(),
11958                window,
11959                cx,
11960            )
11961        })
11962        .unwrap();
11963
11964    let fake_server = fake_servers.next().await.unwrap();
11965    fake_server
11966        .server
11967        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11968            Ok(Some(vec![lsp::TextEdit::new(
11969                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11970                format!("[{} formatted]", params.text_document.uri),
11971            )]))
11972        })
11973        .detach();
11974    save.await;
11975
11976    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11977    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11978    assert_eq!(
11979        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11980        uri!(
11981            "a|o[file:///a/main.rs formatted]bbbb\ncccc\n\nffff\ngggg\n\njjjj\n\nlll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|\nr\n\nuuuu\n\nvvvv\nwwww\nxxxx\n\n{{{{\n||||\n\n\u{7f}\u{7f}\u{7f}\u{7f}"
11982        ),
11983    );
11984    buffer_1.update(cx, |buffer, _| {
11985        assert!(!buffer.is_dirty());
11986        assert_eq!(
11987            buffer.text(),
11988            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11989        )
11990    });
11991    buffer_2.update(cx, |buffer, _| {
11992        assert!(!buffer.is_dirty());
11993        assert_eq!(
11994            buffer.text(),
11995            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11996        )
11997    });
11998    buffer_3.update(cx, |buffer, _| {
11999        assert!(!buffer.is_dirty());
12000        assert_eq!(buffer.text(), sample_text_3,)
12001    });
12002}
12003
12004#[gpui::test]
12005async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12006    init_test(cx, |_| {});
12007
12008    let fs = FakeFs::new(cx.executor());
12009    fs.insert_tree(
12010        path!("/dir"),
12011        json!({
12012            "file1.rs": "fn main() { println!(\"hello\"); }",
12013            "file2.rs": "fn test() { println!(\"test\"); }",
12014            "file3.rs": "fn other() { println!(\"other\"); }\n",
12015        }),
12016    )
12017    .await;
12018
12019    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12020    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12021    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12022
12023    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12024    language_registry.add(rust_lang());
12025
12026    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12027    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12028
12029    // Open three buffers
12030    let buffer_1 = project
12031        .update(cx, |project, cx| {
12032            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12033        })
12034        .await
12035        .unwrap();
12036    let buffer_2 = project
12037        .update(cx, |project, cx| {
12038            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12039        })
12040        .await
12041        .unwrap();
12042    let buffer_3 = project
12043        .update(cx, |project, cx| {
12044            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12045        })
12046        .await
12047        .unwrap();
12048
12049    // Create a multi-buffer with all three buffers
12050    let multi_buffer = cx.new(|cx| {
12051        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12052        multi_buffer.push_excerpts(
12053            buffer_1.clone(),
12054            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12055            cx,
12056        );
12057        multi_buffer.push_excerpts(
12058            buffer_2.clone(),
12059            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12060            cx,
12061        );
12062        multi_buffer.push_excerpts(
12063            buffer_3.clone(),
12064            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12065            cx,
12066        );
12067        multi_buffer
12068    });
12069
12070    let editor = cx.new_window_entity(|window, cx| {
12071        Editor::new(
12072            EditorMode::full(),
12073            multi_buffer,
12074            Some(project.clone()),
12075            window,
12076            cx,
12077        )
12078    });
12079
12080    // Edit only the first buffer
12081    editor.update_in(cx, |editor, window, cx| {
12082        editor.change_selections(
12083            SelectionEffects::scroll(Autoscroll::Next),
12084            window,
12085            cx,
12086            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12087        );
12088        editor.insert("// edited", window, cx);
12089    });
12090
12091    // Verify that only buffer 1 is dirty
12092    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12093    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12094    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12095
12096    // Get write counts after file creation (files were created with initial content)
12097    // We expect each file to have been written once during creation
12098    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12099    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12100    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12101
12102    // Perform autosave
12103    let save_task = editor.update_in(cx, |editor, window, cx| {
12104        editor.save(
12105            SaveOptions {
12106                format: true,
12107                autosave: true,
12108            },
12109            project.clone(),
12110            window,
12111            cx,
12112        )
12113    });
12114    save_task.await.unwrap();
12115
12116    // Only the dirty buffer should have been saved
12117    assert_eq!(
12118        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12119        1,
12120        "Buffer 1 was dirty, so it should have been written once during autosave"
12121    );
12122    assert_eq!(
12123        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12124        0,
12125        "Buffer 2 was clean, so it should not have been written during autosave"
12126    );
12127    assert_eq!(
12128        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12129        0,
12130        "Buffer 3 was clean, so it should not have been written during autosave"
12131    );
12132
12133    // Verify buffer states after autosave
12134    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12135    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12136    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12137
12138    // Now perform a manual save (format = true)
12139    let save_task = editor.update_in(cx, |editor, window, cx| {
12140        editor.save(
12141            SaveOptions {
12142                format: true,
12143                autosave: false,
12144            },
12145            project.clone(),
12146            window,
12147            cx,
12148        )
12149    });
12150    save_task.await.unwrap();
12151
12152    // During manual save, clean buffers don't get written to disk
12153    // They just get did_save called for language server notifications
12154    assert_eq!(
12155        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12156        1,
12157        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12158    );
12159    assert_eq!(
12160        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12161        0,
12162        "Buffer 2 should not have been written at all"
12163    );
12164    assert_eq!(
12165        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12166        0,
12167        "Buffer 3 should not have been written at all"
12168    );
12169}
12170
12171async fn setup_range_format_test(
12172    cx: &mut TestAppContext,
12173) -> (
12174    Entity<Project>,
12175    Entity<Editor>,
12176    &mut gpui::VisualTestContext,
12177    lsp::FakeLanguageServer,
12178) {
12179    init_test(cx, |_| {});
12180
12181    let fs = FakeFs::new(cx.executor());
12182    fs.insert_file(path!("/file.rs"), Default::default()).await;
12183
12184    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12185
12186    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12187    language_registry.add(rust_lang());
12188    let mut fake_servers = language_registry.register_fake_lsp(
12189        "Rust",
12190        FakeLspAdapter {
12191            capabilities: lsp::ServerCapabilities {
12192                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12193                ..lsp::ServerCapabilities::default()
12194            },
12195            ..FakeLspAdapter::default()
12196        },
12197    );
12198
12199    let buffer = project
12200        .update(cx, |project, cx| {
12201            project.open_local_buffer(path!("/file.rs"), cx)
12202        })
12203        .await
12204        .unwrap();
12205
12206    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12207    let (editor, cx) = cx.add_window_view(|window, cx| {
12208        build_editor_with_project(project.clone(), buffer, window, cx)
12209    });
12210
12211    cx.executor().start_waiting();
12212    let fake_server = fake_servers.next().await.unwrap();
12213
12214    (project, editor, cx, fake_server)
12215}
12216
12217#[gpui::test]
12218async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12219    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12220
12221    editor.update_in(cx, |editor, window, cx| {
12222        editor.set_text("one\ntwo\nthree\n", window, cx)
12223    });
12224    assert!(cx.read(|cx| editor.is_dirty(cx)));
12225
12226    let save = editor
12227        .update_in(cx, |editor, window, cx| {
12228            editor.save(
12229                SaveOptions {
12230                    format: true,
12231                    autosave: false,
12232                },
12233                project.clone(),
12234                window,
12235                cx,
12236            )
12237        })
12238        .unwrap();
12239    fake_server
12240        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12241            assert_eq!(
12242                params.text_document.uri,
12243                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12244            );
12245            assert_eq!(params.options.tab_size, 4);
12246            Ok(Some(vec![lsp::TextEdit::new(
12247                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12248                ", ".to_string(),
12249            )]))
12250        })
12251        .next()
12252        .await;
12253    cx.executor().start_waiting();
12254    save.await;
12255    assert_eq!(
12256        editor.update(cx, |editor, cx| editor.text(cx)),
12257        "one, two\nthree\n"
12258    );
12259    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12260}
12261
12262#[gpui::test]
12263async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12264    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12265
12266    editor.update_in(cx, |editor, window, cx| {
12267        editor.set_text("one\ntwo\nthree\n", window, cx)
12268    });
12269    assert!(cx.read(|cx| editor.is_dirty(cx)));
12270
12271    // Test that save still works when formatting hangs
12272    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12273        move |params, _| async move {
12274            assert_eq!(
12275                params.text_document.uri,
12276                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12277            );
12278            futures::future::pending::<()>().await;
12279            unreachable!()
12280        },
12281    );
12282    let save = editor
12283        .update_in(cx, |editor, window, cx| {
12284            editor.save(
12285                SaveOptions {
12286                    format: true,
12287                    autosave: false,
12288                },
12289                project.clone(),
12290                window,
12291                cx,
12292            )
12293        })
12294        .unwrap();
12295    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12296    cx.executor().start_waiting();
12297    save.await;
12298    assert_eq!(
12299        editor.update(cx, |editor, cx| editor.text(cx)),
12300        "one\ntwo\nthree\n"
12301    );
12302    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12303}
12304
12305#[gpui::test]
12306async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12307    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12308
12309    // Buffer starts clean, no formatting should be requested
12310    let save = editor
12311        .update_in(cx, |editor, window, cx| {
12312            editor.save(
12313                SaveOptions {
12314                    format: false,
12315                    autosave: false,
12316                },
12317                project.clone(),
12318                window,
12319                cx,
12320            )
12321        })
12322        .unwrap();
12323    let _pending_format_request = fake_server
12324        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12325            panic!("Should not be invoked");
12326        })
12327        .next();
12328    cx.executor().start_waiting();
12329    save.await;
12330    cx.run_until_parked();
12331}
12332
12333#[gpui::test]
12334async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12335    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12336
12337    // Set Rust language override and assert overridden tabsize is sent to language server
12338    update_test_language_settings(cx, |settings| {
12339        settings.languages.0.insert(
12340            "Rust".into(),
12341            LanguageSettingsContent {
12342                tab_size: NonZeroU32::new(8),
12343                ..Default::default()
12344            },
12345        );
12346    });
12347
12348    editor.update_in(cx, |editor, window, cx| {
12349        editor.set_text("something_new\n", window, cx)
12350    });
12351    assert!(cx.read(|cx| editor.is_dirty(cx)));
12352    let save = editor
12353        .update_in(cx, |editor, window, cx| {
12354            editor.save(
12355                SaveOptions {
12356                    format: true,
12357                    autosave: false,
12358                },
12359                project.clone(),
12360                window,
12361                cx,
12362            )
12363        })
12364        .unwrap();
12365    fake_server
12366        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12367            assert_eq!(
12368                params.text_document.uri,
12369                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12370            );
12371            assert_eq!(params.options.tab_size, 8);
12372            Ok(Some(Vec::new()))
12373        })
12374        .next()
12375        .await;
12376    save.await;
12377}
12378
12379#[gpui::test]
12380async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12381    init_test(cx, |settings| {
12382        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12383            settings::LanguageServerFormatterSpecifier::Current,
12384        )))
12385    });
12386
12387    let fs = FakeFs::new(cx.executor());
12388    fs.insert_file(path!("/file.rs"), Default::default()).await;
12389
12390    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12391
12392    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12393    language_registry.add(Arc::new(Language::new(
12394        LanguageConfig {
12395            name: "Rust".into(),
12396            matcher: LanguageMatcher {
12397                path_suffixes: vec!["rs".to_string()],
12398                ..Default::default()
12399            },
12400            ..LanguageConfig::default()
12401        },
12402        Some(tree_sitter_rust::LANGUAGE.into()),
12403    )));
12404    update_test_language_settings(cx, |settings| {
12405        // Enable Prettier formatting for the same buffer, and ensure
12406        // LSP is called instead of Prettier.
12407        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12408    });
12409    let mut fake_servers = language_registry.register_fake_lsp(
12410        "Rust",
12411        FakeLspAdapter {
12412            capabilities: lsp::ServerCapabilities {
12413                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12414                ..Default::default()
12415            },
12416            ..Default::default()
12417        },
12418    );
12419
12420    let buffer = project
12421        .update(cx, |project, cx| {
12422            project.open_local_buffer(path!("/file.rs"), cx)
12423        })
12424        .await
12425        .unwrap();
12426
12427    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12428    let (editor, cx) = cx.add_window_view(|window, cx| {
12429        build_editor_with_project(project.clone(), buffer, window, cx)
12430    });
12431    editor.update_in(cx, |editor, window, cx| {
12432        editor.set_text("one\ntwo\nthree\n", window, cx)
12433    });
12434
12435    cx.executor().start_waiting();
12436    let fake_server = fake_servers.next().await.unwrap();
12437
12438    let format = editor
12439        .update_in(cx, |editor, window, cx| {
12440            editor.perform_format(
12441                project.clone(),
12442                FormatTrigger::Manual,
12443                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12444                window,
12445                cx,
12446            )
12447        })
12448        .unwrap();
12449    fake_server
12450        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12451            assert_eq!(
12452                params.text_document.uri,
12453                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12454            );
12455            assert_eq!(params.options.tab_size, 4);
12456            Ok(Some(vec![lsp::TextEdit::new(
12457                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12458                ", ".to_string(),
12459            )]))
12460        })
12461        .next()
12462        .await;
12463    cx.executor().start_waiting();
12464    format.await;
12465    assert_eq!(
12466        editor.update(cx, |editor, cx| editor.text(cx)),
12467        "one, two\nthree\n"
12468    );
12469
12470    editor.update_in(cx, |editor, window, cx| {
12471        editor.set_text("one\ntwo\nthree\n", window, cx)
12472    });
12473    // Ensure we don't lock if formatting hangs.
12474    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12475        move |params, _| async move {
12476            assert_eq!(
12477                params.text_document.uri,
12478                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12479            );
12480            futures::future::pending::<()>().await;
12481            unreachable!()
12482        },
12483    );
12484    let format = editor
12485        .update_in(cx, |editor, window, cx| {
12486            editor.perform_format(
12487                project,
12488                FormatTrigger::Manual,
12489                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12490                window,
12491                cx,
12492            )
12493        })
12494        .unwrap();
12495    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12496    cx.executor().start_waiting();
12497    format.await;
12498    assert_eq!(
12499        editor.update(cx, |editor, cx| editor.text(cx)),
12500        "one\ntwo\nthree\n"
12501    );
12502}
12503
12504#[gpui::test]
12505async fn test_multiple_formatters(cx: &mut TestAppContext) {
12506    init_test(cx, |settings| {
12507        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12508        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12509            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12510            Formatter::CodeAction("code-action-1".into()),
12511            Formatter::CodeAction("code-action-2".into()),
12512        ]))
12513    });
12514
12515    let fs = FakeFs::new(cx.executor());
12516    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12517        .await;
12518
12519    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12520    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12521    language_registry.add(rust_lang());
12522
12523    let mut fake_servers = language_registry.register_fake_lsp(
12524        "Rust",
12525        FakeLspAdapter {
12526            capabilities: lsp::ServerCapabilities {
12527                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12528                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12529                    commands: vec!["the-command-for-code-action-1".into()],
12530                    ..Default::default()
12531                }),
12532                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12533                ..Default::default()
12534            },
12535            ..Default::default()
12536        },
12537    );
12538
12539    let buffer = project
12540        .update(cx, |project, cx| {
12541            project.open_local_buffer(path!("/file.rs"), cx)
12542        })
12543        .await
12544        .unwrap();
12545
12546    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12547    let (editor, cx) = cx.add_window_view(|window, cx| {
12548        build_editor_with_project(project.clone(), buffer, window, cx)
12549    });
12550
12551    cx.executor().start_waiting();
12552
12553    let fake_server = fake_servers.next().await.unwrap();
12554    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12555        move |_params, _| async move {
12556            Ok(Some(vec![lsp::TextEdit::new(
12557                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12558                "applied-formatting\n".to_string(),
12559            )]))
12560        },
12561    );
12562    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12563        move |params, _| async move {
12564            let requested_code_actions = params.context.only.expect("Expected code action request");
12565            assert_eq!(requested_code_actions.len(), 1);
12566
12567            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12568            let code_action = match requested_code_actions[0].as_str() {
12569                "code-action-1" => lsp::CodeAction {
12570                    kind: Some("code-action-1".into()),
12571                    edit: Some(lsp::WorkspaceEdit::new(
12572                        [(
12573                            uri,
12574                            vec![lsp::TextEdit::new(
12575                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12576                                "applied-code-action-1-edit\n".to_string(),
12577                            )],
12578                        )]
12579                        .into_iter()
12580                        .collect(),
12581                    )),
12582                    command: Some(lsp::Command {
12583                        command: "the-command-for-code-action-1".into(),
12584                        ..Default::default()
12585                    }),
12586                    ..Default::default()
12587                },
12588                "code-action-2" => lsp::CodeAction {
12589                    kind: Some("code-action-2".into()),
12590                    edit: Some(lsp::WorkspaceEdit::new(
12591                        [(
12592                            uri,
12593                            vec![lsp::TextEdit::new(
12594                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12595                                "applied-code-action-2-edit\n".to_string(),
12596                            )],
12597                        )]
12598                        .into_iter()
12599                        .collect(),
12600                    )),
12601                    ..Default::default()
12602                },
12603                req => panic!("Unexpected code action request: {:?}", req),
12604            };
12605            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12606                code_action,
12607            )]))
12608        },
12609    );
12610
12611    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12612        move |params, _| async move { Ok(params) }
12613    });
12614
12615    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12616    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12617        let fake = fake_server.clone();
12618        let lock = command_lock.clone();
12619        move |params, _| {
12620            assert_eq!(params.command, "the-command-for-code-action-1");
12621            let fake = fake.clone();
12622            let lock = lock.clone();
12623            async move {
12624                lock.lock().await;
12625                fake.server
12626                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12627                        label: None,
12628                        edit: lsp::WorkspaceEdit {
12629                            changes: Some(
12630                                [(
12631                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12632                                    vec![lsp::TextEdit {
12633                                        range: lsp::Range::new(
12634                                            lsp::Position::new(0, 0),
12635                                            lsp::Position::new(0, 0),
12636                                        ),
12637                                        new_text: "applied-code-action-1-command\n".into(),
12638                                    }],
12639                                )]
12640                                .into_iter()
12641                                .collect(),
12642                            ),
12643                            ..Default::default()
12644                        },
12645                    })
12646                    .await
12647                    .into_response()
12648                    .unwrap();
12649                Ok(Some(json!(null)))
12650            }
12651        }
12652    });
12653
12654    cx.executor().start_waiting();
12655    editor
12656        .update_in(cx, |editor, window, cx| {
12657            editor.perform_format(
12658                project.clone(),
12659                FormatTrigger::Manual,
12660                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12661                window,
12662                cx,
12663            )
12664        })
12665        .unwrap()
12666        .await;
12667    editor.update(cx, |editor, cx| {
12668        assert_eq!(
12669            editor.text(cx),
12670            r#"
12671                applied-code-action-2-edit
12672                applied-code-action-1-command
12673                applied-code-action-1-edit
12674                applied-formatting
12675                one
12676                two
12677                three
12678            "#
12679            .unindent()
12680        );
12681    });
12682
12683    editor.update_in(cx, |editor, window, cx| {
12684        editor.undo(&Default::default(), window, cx);
12685        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12686    });
12687
12688    // Perform a manual edit while waiting for an LSP command
12689    // that's being run as part of a formatting code action.
12690    let lock_guard = command_lock.lock().await;
12691    let format = editor
12692        .update_in(cx, |editor, window, cx| {
12693            editor.perform_format(
12694                project.clone(),
12695                FormatTrigger::Manual,
12696                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12697                window,
12698                cx,
12699            )
12700        })
12701        .unwrap();
12702    cx.run_until_parked();
12703    editor.update(cx, |editor, cx| {
12704        assert_eq!(
12705            editor.text(cx),
12706            r#"
12707                applied-code-action-1-edit
12708                applied-formatting
12709                one
12710                two
12711                three
12712            "#
12713            .unindent()
12714        );
12715
12716        editor.buffer.update(cx, |buffer, cx| {
12717            let ix = buffer.len(cx);
12718            buffer.edit([(ix..ix, "edited\n")], None, cx);
12719        });
12720    });
12721
12722    // Allow the LSP command to proceed. Because the buffer was edited,
12723    // the second code action will not be run.
12724    drop(lock_guard);
12725    format.await;
12726    editor.update_in(cx, |editor, window, cx| {
12727        assert_eq!(
12728            editor.text(cx),
12729            r#"
12730                applied-code-action-1-command
12731                applied-code-action-1-edit
12732                applied-formatting
12733                one
12734                two
12735                three
12736                edited
12737            "#
12738            .unindent()
12739        );
12740
12741        // The manual edit is undone first, because it is the last thing the user did
12742        // (even though the command completed afterwards).
12743        editor.undo(&Default::default(), window, cx);
12744        assert_eq!(
12745            editor.text(cx),
12746            r#"
12747                applied-code-action-1-command
12748                applied-code-action-1-edit
12749                applied-formatting
12750                one
12751                two
12752                three
12753            "#
12754            .unindent()
12755        );
12756
12757        // All the formatting (including the command, which completed after the manual edit)
12758        // is undone together.
12759        editor.undo(&Default::default(), window, cx);
12760        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12761    });
12762}
12763
12764#[gpui::test]
12765async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12766    init_test(cx, |settings| {
12767        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12768            settings::LanguageServerFormatterSpecifier::Current,
12769        )]))
12770    });
12771
12772    let fs = FakeFs::new(cx.executor());
12773    fs.insert_file(path!("/file.ts"), Default::default()).await;
12774
12775    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12776
12777    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12778    language_registry.add(Arc::new(Language::new(
12779        LanguageConfig {
12780            name: "TypeScript".into(),
12781            matcher: LanguageMatcher {
12782                path_suffixes: vec!["ts".to_string()],
12783                ..Default::default()
12784            },
12785            ..LanguageConfig::default()
12786        },
12787        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12788    )));
12789    update_test_language_settings(cx, |settings| {
12790        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12791    });
12792    let mut fake_servers = language_registry.register_fake_lsp(
12793        "TypeScript",
12794        FakeLspAdapter {
12795            capabilities: lsp::ServerCapabilities {
12796                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12797                ..Default::default()
12798            },
12799            ..Default::default()
12800        },
12801    );
12802
12803    let buffer = project
12804        .update(cx, |project, cx| {
12805            project.open_local_buffer(path!("/file.ts"), cx)
12806        })
12807        .await
12808        .unwrap();
12809
12810    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12811    let (editor, cx) = cx.add_window_view(|window, cx| {
12812        build_editor_with_project(project.clone(), buffer, window, cx)
12813    });
12814    editor.update_in(cx, |editor, window, cx| {
12815        editor.set_text(
12816            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12817            window,
12818            cx,
12819        )
12820    });
12821
12822    cx.executor().start_waiting();
12823    let fake_server = fake_servers.next().await.unwrap();
12824
12825    let format = editor
12826        .update_in(cx, |editor, window, cx| {
12827            editor.perform_code_action_kind(
12828                project.clone(),
12829                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12830                window,
12831                cx,
12832            )
12833        })
12834        .unwrap();
12835    fake_server
12836        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12837            assert_eq!(
12838                params.text_document.uri,
12839                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12840            );
12841            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12842                lsp::CodeAction {
12843                    title: "Organize Imports".to_string(),
12844                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12845                    edit: Some(lsp::WorkspaceEdit {
12846                        changes: Some(
12847                            [(
12848                                params.text_document.uri.clone(),
12849                                vec![lsp::TextEdit::new(
12850                                    lsp::Range::new(
12851                                        lsp::Position::new(1, 0),
12852                                        lsp::Position::new(2, 0),
12853                                    ),
12854                                    "".to_string(),
12855                                )],
12856                            )]
12857                            .into_iter()
12858                            .collect(),
12859                        ),
12860                        ..Default::default()
12861                    }),
12862                    ..Default::default()
12863                },
12864            )]))
12865        })
12866        .next()
12867        .await;
12868    cx.executor().start_waiting();
12869    format.await;
12870    assert_eq!(
12871        editor.update(cx, |editor, cx| editor.text(cx)),
12872        "import { a } from 'module';\n\nconst x = a;\n"
12873    );
12874
12875    editor.update_in(cx, |editor, window, cx| {
12876        editor.set_text(
12877            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12878            window,
12879            cx,
12880        )
12881    });
12882    // Ensure we don't lock if code action hangs.
12883    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12884        move |params, _| async move {
12885            assert_eq!(
12886                params.text_document.uri,
12887                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12888            );
12889            futures::future::pending::<()>().await;
12890            unreachable!()
12891        },
12892    );
12893    let format = editor
12894        .update_in(cx, |editor, window, cx| {
12895            editor.perform_code_action_kind(
12896                project,
12897                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12898                window,
12899                cx,
12900            )
12901        })
12902        .unwrap();
12903    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12904    cx.executor().start_waiting();
12905    format.await;
12906    assert_eq!(
12907        editor.update(cx, |editor, cx| editor.text(cx)),
12908        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12909    );
12910}
12911
12912#[gpui::test]
12913async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12914    init_test(cx, |_| {});
12915
12916    let mut cx = EditorLspTestContext::new_rust(
12917        lsp::ServerCapabilities {
12918            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12919            ..Default::default()
12920        },
12921        cx,
12922    )
12923    .await;
12924
12925    cx.set_state(indoc! {"
12926        one.twoˇ
12927    "});
12928
12929    // The format request takes a long time. When it completes, it inserts
12930    // a newline and an indent before the `.`
12931    cx.lsp
12932        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12933            let executor = cx.background_executor().clone();
12934            async move {
12935                executor.timer(Duration::from_millis(100)).await;
12936                Ok(Some(vec![lsp::TextEdit {
12937                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12938                    new_text: "\n    ".into(),
12939                }]))
12940            }
12941        });
12942
12943    // Submit a format request.
12944    let format_1 = cx
12945        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12946        .unwrap();
12947    cx.executor().run_until_parked();
12948
12949    // Submit a second format request.
12950    let format_2 = cx
12951        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12952        .unwrap();
12953    cx.executor().run_until_parked();
12954
12955    // Wait for both format requests to complete
12956    cx.executor().advance_clock(Duration::from_millis(200));
12957    cx.executor().start_waiting();
12958    format_1.await.unwrap();
12959    cx.executor().start_waiting();
12960    format_2.await.unwrap();
12961
12962    // The formatting edits only happens once.
12963    cx.assert_editor_state(indoc! {"
12964        one
12965            .twoˇ
12966    "});
12967}
12968
12969#[gpui::test]
12970async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12971    init_test(cx, |settings| {
12972        settings.defaults.formatter = Some(FormatterList::default())
12973    });
12974
12975    let mut cx = EditorLspTestContext::new_rust(
12976        lsp::ServerCapabilities {
12977            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12978            ..Default::default()
12979        },
12980        cx,
12981    )
12982    .await;
12983
12984    // Record which buffer changes have been sent to the language server
12985    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12986    cx.lsp
12987        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12988            let buffer_changes = buffer_changes.clone();
12989            move |params, _| {
12990                buffer_changes.lock().extend(
12991                    params
12992                        .content_changes
12993                        .into_iter()
12994                        .map(|e| (e.range.unwrap(), e.text)),
12995                );
12996            }
12997        });
12998    // Handle formatting requests to the language server.
12999    cx.lsp
13000        .set_request_handler::<lsp::request::Formatting, _, _>({
13001            let buffer_changes = buffer_changes.clone();
13002            move |_, _| {
13003                let buffer_changes = buffer_changes.clone();
13004                // Insert blank lines between each line of the buffer.
13005                async move {
13006                    // When formatting is requested, trailing whitespace has already been stripped,
13007                    // and the trailing newline has already been added.
13008                    assert_eq!(
13009                        &buffer_changes.lock()[1..],
13010                        &[
13011                            (
13012                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13013                                "".into()
13014                            ),
13015                            (
13016                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13017                                "".into()
13018                            ),
13019                            (
13020                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13021                                "\n".into()
13022                            ),
13023                        ]
13024                    );
13025
13026                    Ok(Some(vec![
13027                        lsp::TextEdit {
13028                            range: lsp::Range::new(
13029                                lsp::Position::new(1, 0),
13030                                lsp::Position::new(1, 0),
13031                            ),
13032                            new_text: "\n".into(),
13033                        },
13034                        lsp::TextEdit {
13035                            range: lsp::Range::new(
13036                                lsp::Position::new(2, 0),
13037                                lsp::Position::new(2, 0),
13038                            ),
13039                            new_text: "\n".into(),
13040                        },
13041                    ]))
13042                }
13043            }
13044        });
13045
13046    // Set up a buffer white some trailing whitespace and no trailing newline.
13047    cx.set_state(
13048        &[
13049            "one ",   //
13050            "twoˇ",   //
13051            "three ", //
13052            "four",   //
13053        ]
13054        .join("\n"),
13055    );
13056    cx.run_until_parked();
13057
13058    // Submit a format request.
13059    let format = cx
13060        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13061        .unwrap();
13062
13063    cx.run_until_parked();
13064    // After formatting the buffer, the trailing whitespace is stripped,
13065    // a newline is appended, and the edits provided by the language server
13066    // have been applied.
13067    format.await.unwrap();
13068
13069    cx.assert_editor_state(
13070        &[
13071            "one",   //
13072            "",      //
13073            "twoˇ",  //
13074            "",      //
13075            "three", //
13076            "four",  //
13077            "",      //
13078        ]
13079        .join("\n"),
13080    );
13081
13082    // Undoing the formatting undoes the trailing whitespace removal, the
13083    // trailing newline, and the LSP edits.
13084    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13085    cx.assert_editor_state(
13086        &[
13087            "one ",   //
13088            "twoˇ",   //
13089            "three ", //
13090            "four",   //
13091        ]
13092        .join("\n"),
13093    );
13094}
13095
13096#[gpui::test]
13097async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13098    cx: &mut TestAppContext,
13099) {
13100    init_test(cx, |_| {});
13101
13102    cx.update(|cx| {
13103        cx.update_global::<SettingsStore, _>(|settings, cx| {
13104            settings.update_user_settings(cx, |settings| {
13105                settings.editor.auto_signature_help = Some(true);
13106            });
13107        });
13108    });
13109
13110    let mut cx = EditorLspTestContext::new_rust(
13111        lsp::ServerCapabilities {
13112            signature_help_provider: Some(lsp::SignatureHelpOptions {
13113                ..Default::default()
13114            }),
13115            ..Default::default()
13116        },
13117        cx,
13118    )
13119    .await;
13120
13121    let language = Language::new(
13122        LanguageConfig {
13123            name: "Rust".into(),
13124            brackets: BracketPairConfig {
13125                pairs: vec![
13126                    BracketPair {
13127                        start: "{".to_string(),
13128                        end: "}".to_string(),
13129                        close: true,
13130                        surround: true,
13131                        newline: true,
13132                    },
13133                    BracketPair {
13134                        start: "(".to_string(),
13135                        end: ")".to_string(),
13136                        close: true,
13137                        surround: true,
13138                        newline: true,
13139                    },
13140                    BracketPair {
13141                        start: "/*".to_string(),
13142                        end: " */".to_string(),
13143                        close: true,
13144                        surround: true,
13145                        newline: true,
13146                    },
13147                    BracketPair {
13148                        start: "[".to_string(),
13149                        end: "]".to_string(),
13150                        close: false,
13151                        surround: false,
13152                        newline: true,
13153                    },
13154                    BracketPair {
13155                        start: "\"".to_string(),
13156                        end: "\"".to_string(),
13157                        close: true,
13158                        surround: true,
13159                        newline: false,
13160                    },
13161                    BracketPair {
13162                        start: "<".to_string(),
13163                        end: ">".to_string(),
13164                        close: false,
13165                        surround: true,
13166                        newline: true,
13167                    },
13168                ],
13169                ..Default::default()
13170            },
13171            autoclose_before: "})]".to_string(),
13172            ..Default::default()
13173        },
13174        Some(tree_sitter_rust::LANGUAGE.into()),
13175    );
13176    let language = Arc::new(language);
13177
13178    cx.language_registry().add(language.clone());
13179    cx.update_buffer(|buffer, cx| {
13180        buffer.set_language(Some(language), cx);
13181    });
13182
13183    cx.set_state(
13184        &r#"
13185            fn main() {
13186                sampleˇ
13187            }
13188        "#
13189        .unindent(),
13190    );
13191
13192    cx.update_editor(|editor, window, cx| {
13193        editor.handle_input("(", window, cx);
13194    });
13195    cx.assert_editor_state(
13196        &"
13197            fn main() {
13198                sample(ˇ)
13199            }
13200        "
13201        .unindent(),
13202    );
13203
13204    let mocked_response = lsp::SignatureHelp {
13205        signatures: vec![lsp::SignatureInformation {
13206            label: "fn sample(param1: u8, param2: u8)".to_string(),
13207            documentation: None,
13208            parameters: Some(vec![
13209                lsp::ParameterInformation {
13210                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13211                    documentation: None,
13212                },
13213                lsp::ParameterInformation {
13214                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13215                    documentation: None,
13216                },
13217            ]),
13218            active_parameter: None,
13219        }],
13220        active_signature: Some(0),
13221        active_parameter: Some(0),
13222    };
13223    handle_signature_help_request(&mut cx, mocked_response).await;
13224
13225    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13226        .await;
13227
13228    cx.editor(|editor, _, _| {
13229        let signature_help_state = editor.signature_help_state.popover().cloned();
13230        let signature = signature_help_state.unwrap();
13231        assert_eq!(
13232            signature.signatures[signature.current_signature].label,
13233            "fn sample(param1: u8, param2: u8)"
13234        );
13235    });
13236}
13237
13238#[gpui::test]
13239async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13240    init_test(cx, |_| {});
13241
13242    cx.update(|cx| {
13243        cx.update_global::<SettingsStore, _>(|settings, cx| {
13244            settings.update_user_settings(cx, |settings| {
13245                settings.editor.auto_signature_help = Some(false);
13246                settings.editor.show_signature_help_after_edits = Some(false);
13247            });
13248        });
13249    });
13250
13251    let mut cx = EditorLspTestContext::new_rust(
13252        lsp::ServerCapabilities {
13253            signature_help_provider: Some(lsp::SignatureHelpOptions {
13254                ..Default::default()
13255            }),
13256            ..Default::default()
13257        },
13258        cx,
13259    )
13260    .await;
13261
13262    let language = Language::new(
13263        LanguageConfig {
13264            name: "Rust".into(),
13265            brackets: BracketPairConfig {
13266                pairs: vec![
13267                    BracketPair {
13268                        start: "{".to_string(),
13269                        end: "}".to_string(),
13270                        close: true,
13271                        surround: true,
13272                        newline: true,
13273                    },
13274                    BracketPair {
13275                        start: "(".to_string(),
13276                        end: ")".to_string(),
13277                        close: true,
13278                        surround: true,
13279                        newline: true,
13280                    },
13281                    BracketPair {
13282                        start: "/*".to_string(),
13283                        end: " */".to_string(),
13284                        close: true,
13285                        surround: true,
13286                        newline: true,
13287                    },
13288                    BracketPair {
13289                        start: "[".to_string(),
13290                        end: "]".to_string(),
13291                        close: false,
13292                        surround: false,
13293                        newline: true,
13294                    },
13295                    BracketPair {
13296                        start: "\"".to_string(),
13297                        end: "\"".to_string(),
13298                        close: true,
13299                        surround: true,
13300                        newline: false,
13301                    },
13302                    BracketPair {
13303                        start: "<".to_string(),
13304                        end: ">".to_string(),
13305                        close: false,
13306                        surround: true,
13307                        newline: true,
13308                    },
13309                ],
13310                ..Default::default()
13311            },
13312            autoclose_before: "})]".to_string(),
13313            ..Default::default()
13314        },
13315        Some(tree_sitter_rust::LANGUAGE.into()),
13316    );
13317    let language = Arc::new(language);
13318
13319    cx.language_registry().add(language.clone());
13320    cx.update_buffer(|buffer, cx| {
13321        buffer.set_language(Some(language), cx);
13322    });
13323
13324    // Ensure that signature_help is not called when no signature help is enabled.
13325    cx.set_state(
13326        &r#"
13327            fn main() {
13328                sampleˇ
13329            }
13330        "#
13331        .unindent(),
13332    );
13333    cx.update_editor(|editor, window, cx| {
13334        editor.handle_input("(", window, cx);
13335    });
13336    cx.assert_editor_state(
13337        &"
13338            fn main() {
13339                sample(ˇ)
13340            }
13341        "
13342        .unindent(),
13343    );
13344    cx.editor(|editor, _, _| {
13345        assert!(editor.signature_help_state.task().is_none());
13346    });
13347
13348    let mocked_response = lsp::SignatureHelp {
13349        signatures: vec![lsp::SignatureInformation {
13350            label: "fn sample(param1: u8, param2: u8)".to_string(),
13351            documentation: None,
13352            parameters: Some(vec![
13353                lsp::ParameterInformation {
13354                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13355                    documentation: None,
13356                },
13357                lsp::ParameterInformation {
13358                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13359                    documentation: None,
13360                },
13361            ]),
13362            active_parameter: None,
13363        }],
13364        active_signature: Some(0),
13365        active_parameter: Some(0),
13366    };
13367
13368    // Ensure that signature_help is called when enabled afte edits
13369    cx.update(|_, cx| {
13370        cx.update_global::<SettingsStore, _>(|settings, cx| {
13371            settings.update_user_settings(cx, |settings| {
13372                settings.editor.auto_signature_help = Some(false);
13373                settings.editor.show_signature_help_after_edits = Some(true);
13374            });
13375        });
13376    });
13377    cx.set_state(
13378        &r#"
13379            fn main() {
13380                sampleˇ
13381            }
13382        "#
13383        .unindent(),
13384    );
13385    cx.update_editor(|editor, window, cx| {
13386        editor.handle_input("(", window, cx);
13387    });
13388    cx.assert_editor_state(
13389        &"
13390            fn main() {
13391                sample(ˇ)
13392            }
13393        "
13394        .unindent(),
13395    );
13396    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13397    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13398        .await;
13399    cx.update_editor(|editor, _, _| {
13400        let signature_help_state = editor.signature_help_state.popover().cloned();
13401        assert!(signature_help_state.is_some());
13402        let signature = signature_help_state.unwrap();
13403        assert_eq!(
13404            signature.signatures[signature.current_signature].label,
13405            "fn sample(param1: u8, param2: u8)"
13406        );
13407        editor.signature_help_state = SignatureHelpState::default();
13408    });
13409
13410    // Ensure that signature_help is called when auto signature help override is enabled
13411    cx.update(|_, cx| {
13412        cx.update_global::<SettingsStore, _>(|settings, cx| {
13413            settings.update_user_settings(cx, |settings| {
13414                settings.editor.auto_signature_help = Some(true);
13415                settings.editor.show_signature_help_after_edits = Some(false);
13416            });
13417        });
13418    });
13419    cx.set_state(
13420        &r#"
13421            fn main() {
13422                sampleˇ
13423            }
13424        "#
13425        .unindent(),
13426    );
13427    cx.update_editor(|editor, window, cx| {
13428        editor.handle_input("(", window, cx);
13429    });
13430    cx.assert_editor_state(
13431        &"
13432            fn main() {
13433                sample(ˇ)
13434            }
13435        "
13436        .unindent(),
13437    );
13438    handle_signature_help_request(&mut cx, mocked_response).await;
13439    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13440        .await;
13441    cx.editor(|editor, _, _| {
13442        let signature_help_state = editor.signature_help_state.popover().cloned();
13443        assert!(signature_help_state.is_some());
13444        let signature = signature_help_state.unwrap();
13445        assert_eq!(
13446            signature.signatures[signature.current_signature].label,
13447            "fn sample(param1: u8, param2: u8)"
13448        );
13449    });
13450}
13451
13452#[gpui::test]
13453async fn test_signature_help(cx: &mut TestAppContext) {
13454    init_test(cx, |_| {});
13455    cx.update(|cx| {
13456        cx.update_global::<SettingsStore, _>(|settings, cx| {
13457            settings.update_user_settings(cx, |settings| {
13458                settings.editor.auto_signature_help = Some(true);
13459            });
13460        });
13461    });
13462
13463    let mut cx = EditorLspTestContext::new_rust(
13464        lsp::ServerCapabilities {
13465            signature_help_provider: Some(lsp::SignatureHelpOptions {
13466                ..Default::default()
13467            }),
13468            ..Default::default()
13469        },
13470        cx,
13471    )
13472    .await;
13473
13474    // A test that directly calls `show_signature_help`
13475    cx.update_editor(|editor, window, cx| {
13476        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13477    });
13478
13479    let mocked_response = lsp::SignatureHelp {
13480        signatures: vec![lsp::SignatureInformation {
13481            label: "fn sample(param1: u8, param2: u8)".to_string(),
13482            documentation: None,
13483            parameters: Some(vec![
13484                lsp::ParameterInformation {
13485                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13486                    documentation: None,
13487                },
13488                lsp::ParameterInformation {
13489                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13490                    documentation: None,
13491                },
13492            ]),
13493            active_parameter: None,
13494        }],
13495        active_signature: Some(0),
13496        active_parameter: Some(0),
13497    };
13498    handle_signature_help_request(&mut cx, mocked_response).await;
13499
13500    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13501        .await;
13502
13503    cx.editor(|editor, _, _| {
13504        let signature_help_state = editor.signature_help_state.popover().cloned();
13505        assert!(signature_help_state.is_some());
13506        let signature = signature_help_state.unwrap();
13507        assert_eq!(
13508            signature.signatures[signature.current_signature].label,
13509            "fn sample(param1: u8, param2: u8)"
13510        );
13511    });
13512
13513    // When exiting outside from inside the brackets, `signature_help` is closed.
13514    cx.set_state(indoc! {"
13515        fn main() {
13516            sample(ˇ);
13517        }
13518
13519        fn sample(param1: u8, param2: u8) {}
13520    "});
13521
13522    cx.update_editor(|editor, window, cx| {
13523        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13524            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13525        });
13526    });
13527
13528    let mocked_response = lsp::SignatureHelp {
13529        signatures: Vec::new(),
13530        active_signature: None,
13531        active_parameter: None,
13532    };
13533    handle_signature_help_request(&mut cx, mocked_response).await;
13534
13535    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13536        .await;
13537
13538    cx.editor(|editor, _, _| {
13539        assert!(!editor.signature_help_state.is_shown());
13540    });
13541
13542    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13543    cx.set_state(indoc! {"
13544        fn main() {
13545            sample(ˇ);
13546        }
13547
13548        fn sample(param1: u8, param2: u8) {}
13549    "});
13550
13551    let mocked_response = lsp::SignatureHelp {
13552        signatures: vec![lsp::SignatureInformation {
13553            label: "fn sample(param1: u8, param2: u8)".to_string(),
13554            documentation: None,
13555            parameters: Some(vec![
13556                lsp::ParameterInformation {
13557                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13558                    documentation: None,
13559                },
13560                lsp::ParameterInformation {
13561                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13562                    documentation: None,
13563                },
13564            ]),
13565            active_parameter: None,
13566        }],
13567        active_signature: Some(0),
13568        active_parameter: Some(0),
13569    };
13570    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13571    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13572        .await;
13573    cx.editor(|editor, _, _| {
13574        assert!(editor.signature_help_state.is_shown());
13575    });
13576
13577    // Restore the popover with more parameter input
13578    cx.set_state(indoc! {"
13579        fn main() {
13580            sample(param1, param2ˇ);
13581        }
13582
13583        fn sample(param1: u8, param2: u8) {}
13584    "});
13585
13586    let mocked_response = lsp::SignatureHelp {
13587        signatures: vec![lsp::SignatureInformation {
13588            label: "fn sample(param1: u8, param2: u8)".to_string(),
13589            documentation: None,
13590            parameters: Some(vec![
13591                lsp::ParameterInformation {
13592                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13593                    documentation: None,
13594                },
13595                lsp::ParameterInformation {
13596                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13597                    documentation: None,
13598                },
13599            ]),
13600            active_parameter: None,
13601        }],
13602        active_signature: Some(0),
13603        active_parameter: Some(1),
13604    };
13605    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13606    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13607        .await;
13608
13609    // When selecting a range, the popover is gone.
13610    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13611    cx.update_editor(|editor, window, cx| {
13612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13613            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13614        })
13615    });
13616    cx.assert_editor_state(indoc! {"
13617        fn main() {
13618            sample(param1, «ˇparam2»);
13619        }
13620
13621        fn sample(param1: u8, param2: u8) {}
13622    "});
13623    cx.editor(|editor, _, _| {
13624        assert!(!editor.signature_help_state.is_shown());
13625    });
13626
13627    // When unselecting again, the popover is back if within the brackets.
13628    cx.update_editor(|editor, window, cx| {
13629        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13630            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13631        })
13632    });
13633    cx.assert_editor_state(indoc! {"
13634        fn main() {
13635            sample(param1, ˇparam2);
13636        }
13637
13638        fn sample(param1: u8, param2: u8) {}
13639    "});
13640    handle_signature_help_request(&mut cx, mocked_response).await;
13641    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13642        .await;
13643    cx.editor(|editor, _, _| {
13644        assert!(editor.signature_help_state.is_shown());
13645    });
13646
13647    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13648    cx.update_editor(|editor, window, cx| {
13649        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13650            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13651            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13652        })
13653    });
13654    cx.assert_editor_state(indoc! {"
13655        fn main() {
13656            sample(param1, ˇparam2);
13657        }
13658
13659        fn sample(param1: u8, param2: u8) {}
13660    "});
13661
13662    let mocked_response = lsp::SignatureHelp {
13663        signatures: vec![lsp::SignatureInformation {
13664            label: "fn sample(param1: u8, param2: u8)".to_string(),
13665            documentation: None,
13666            parameters: Some(vec![
13667                lsp::ParameterInformation {
13668                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13669                    documentation: None,
13670                },
13671                lsp::ParameterInformation {
13672                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13673                    documentation: None,
13674                },
13675            ]),
13676            active_parameter: None,
13677        }],
13678        active_signature: Some(0),
13679        active_parameter: Some(1),
13680    };
13681    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13682    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13683        .await;
13684    cx.update_editor(|editor, _, cx| {
13685        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13686    });
13687    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13688        .await;
13689    cx.update_editor(|editor, window, cx| {
13690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13691            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13692        })
13693    });
13694    cx.assert_editor_state(indoc! {"
13695        fn main() {
13696            sample(param1, «ˇparam2»);
13697        }
13698
13699        fn sample(param1: u8, param2: u8) {}
13700    "});
13701    cx.update_editor(|editor, window, cx| {
13702        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13703            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13704        })
13705    });
13706    cx.assert_editor_state(indoc! {"
13707        fn main() {
13708            sample(param1, ˇparam2);
13709        }
13710
13711        fn sample(param1: u8, param2: u8) {}
13712    "});
13713    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13714        .await;
13715}
13716
13717#[gpui::test]
13718async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13719    init_test(cx, |_| {});
13720
13721    let mut cx = EditorLspTestContext::new_rust(
13722        lsp::ServerCapabilities {
13723            signature_help_provider: Some(lsp::SignatureHelpOptions {
13724                ..Default::default()
13725            }),
13726            ..Default::default()
13727        },
13728        cx,
13729    )
13730    .await;
13731
13732    cx.set_state(indoc! {"
13733        fn main() {
13734            overloadedˇ
13735        }
13736    "});
13737
13738    cx.update_editor(|editor, window, cx| {
13739        editor.handle_input("(", window, cx);
13740        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13741    });
13742
13743    // Mock response with 3 signatures
13744    let mocked_response = lsp::SignatureHelp {
13745        signatures: vec![
13746            lsp::SignatureInformation {
13747                label: "fn overloaded(x: i32)".to_string(),
13748                documentation: None,
13749                parameters: Some(vec![lsp::ParameterInformation {
13750                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13751                    documentation: None,
13752                }]),
13753                active_parameter: None,
13754            },
13755            lsp::SignatureInformation {
13756                label: "fn overloaded(x: i32, y: i32)".to_string(),
13757                documentation: None,
13758                parameters: Some(vec![
13759                    lsp::ParameterInformation {
13760                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13761                        documentation: None,
13762                    },
13763                    lsp::ParameterInformation {
13764                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13765                        documentation: None,
13766                    },
13767                ]),
13768                active_parameter: None,
13769            },
13770            lsp::SignatureInformation {
13771                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13772                documentation: None,
13773                parameters: Some(vec![
13774                    lsp::ParameterInformation {
13775                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13776                        documentation: None,
13777                    },
13778                    lsp::ParameterInformation {
13779                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13780                        documentation: None,
13781                    },
13782                    lsp::ParameterInformation {
13783                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13784                        documentation: None,
13785                    },
13786                ]),
13787                active_parameter: None,
13788            },
13789        ],
13790        active_signature: Some(1),
13791        active_parameter: Some(0),
13792    };
13793    handle_signature_help_request(&mut cx, mocked_response).await;
13794
13795    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13796        .await;
13797
13798    // Verify we have multiple signatures and the right one is selected
13799    cx.editor(|editor, _, _| {
13800        let popover = editor.signature_help_state.popover().cloned().unwrap();
13801        assert_eq!(popover.signatures.len(), 3);
13802        // active_signature was 1, so that should be the current
13803        assert_eq!(popover.current_signature, 1);
13804        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13805        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13806        assert_eq!(
13807            popover.signatures[2].label,
13808            "fn overloaded(x: i32, y: i32, z: i32)"
13809        );
13810    });
13811
13812    // Test navigation functionality
13813    cx.update_editor(|editor, window, cx| {
13814        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13815    });
13816
13817    cx.editor(|editor, _, _| {
13818        let popover = editor.signature_help_state.popover().cloned().unwrap();
13819        assert_eq!(popover.current_signature, 2);
13820    });
13821
13822    // Test wrap around
13823    cx.update_editor(|editor, window, cx| {
13824        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13825    });
13826
13827    cx.editor(|editor, _, _| {
13828        let popover = editor.signature_help_state.popover().cloned().unwrap();
13829        assert_eq!(popover.current_signature, 0);
13830    });
13831
13832    // Test previous navigation
13833    cx.update_editor(|editor, window, cx| {
13834        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13835    });
13836
13837    cx.editor(|editor, _, _| {
13838        let popover = editor.signature_help_state.popover().cloned().unwrap();
13839        assert_eq!(popover.current_signature, 2);
13840    });
13841}
13842
13843#[gpui::test]
13844async fn test_completion_mode(cx: &mut TestAppContext) {
13845    init_test(cx, |_| {});
13846    let mut cx = EditorLspTestContext::new_rust(
13847        lsp::ServerCapabilities {
13848            completion_provider: Some(lsp::CompletionOptions {
13849                resolve_provider: Some(true),
13850                ..Default::default()
13851            }),
13852            ..Default::default()
13853        },
13854        cx,
13855    )
13856    .await;
13857
13858    struct Run {
13859        run_description: &'static str,
13860        initial_state: String,
13861        buffer_marked_text: String,
13862        completion_label: &'static str,
13863        completion_text: &'static str,
13864        expected_with_insert_mode: String,
13865        expected_with_replace_mode: String,
13866        expected_with_replace_subsequence_mode: String,
13867        expected_with_replace_suffix_mode: String,
13868    }
13869
13870    let runs = [
13871        Run {
13872            run_description: "Start of word matches completion text",
13873            initial_state: "before ediˇ after".into(),
13874            buffer_marked_text: "before <edi|> after".into(),
13875            completion_label: "editor",
13876            completion_text: "editor",
13877            expected_with_insert_mode: "before editorˇ after".into(),
13878            expected_with_replace_mode: "before editorˇ after".into(),
13879            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13880            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13881        },
13882        Run {
13883            run_description: "Accept same text at the middle of the word",
13884            initial_state: "before ediˇtor after".into(),
13885            buffer_marked_text: "before <edi|tor> after".into(),
13886            completion_label: "editor",
13887            completion_text: "editor",
13888            expected_with_insert_mode: "before editorˇtor after".into(),
13889            expected_with_replace_mode: "before editorˇ after".into(),
13890            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13891            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13892        },
13893        Run {
13894            run_description: "End of word matches completion text -- cursor at end",
13895            initial_state: "before torˇ after".into(),
13896            buffer_marked_text: "before <tor|> after".into(),
13897            completion_label: "editor",
13898            completion_text: "editor",
13899            expected_with_insert_mode: "before editorˇ after".into(),
13900            expected_with_replace_mode: "before editorˇ after".into(),
13901            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13902            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13903        },
13904        Run {
13905            run_description: "End of word matches completion text -- cursor at start",
13906            initial_state: "before ˇtor after".into(),
13907            buffer_marked_text: "before <|tor> after".into(),
13908            completion_label: "editor",
13909            completion_text: "editor",
13910            expected_with_insert_mode: "before editorˇtor after".into(),
13911            expected_with_replace_mode: "before editorˇ after".into(),
13912            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13913            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13914        },
13915        Run {
13916            run_description: "Prepend text containing whitespace",
13917            initial_state: "pˇfield: bool".into(),
13918            buffer_marked_text: "<p|field>: bool".into(),
13919            completion_label: "pub ",
13920            completion_text: "pub ",
13921            expected_with_insert_mode: "pub ˇfield: bool".into(),
13922            expected_with_replace_mode: "pub ˇ: bool".into(),
13923            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13924            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13925        },
13926        Run {
13927            run_description: "Add element to start of list",
13928            initial_state: "[element_ˇelement_2]".into(),
13929            buffer_marked_text: "[<element_|element_2>]".into(),
13930            completion_label: "element_1",
13931            completion_text: "element_1",
13932            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13933            expected_with_replace_mode: "[element_1ˇ]".into(),
13934            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13935            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13936        },
13937        Run {
13938            run_description: "Add element to start of list -- first and second elements are equal",
13939            initial_state: "[elˇelement]".into(),
13940            buffer_marked_text: "[<el|element>]".into(),
13941            completion_label: "element",
13942            completion_text: "element",
13943            expected_with_insert_mode: "[elementˇelement]".into(),
13944            expected_with_replace_mode: "[elementˇ]".into(),
13945            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13946            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13947        },
13948        Run {
13949            run_description: "Ends with matching suffix",
13950            initial_state: "SubˇError".into(),
13951            buffer_marked_text: "<Sub|Error>".into(),
13952            completion_label: "SubscriptionError",
13953            completion_text: "SubscriptionError",
13954            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13955            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13956            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13957            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13958        },
13959        Run {
13960            run_description: "Suffix is a subsequence -- contiguous",
13961            initial_state: "SubˇErr".into(),
13962            buffer_marked_text: "<Sub|Err>".into(),
13963            completion_label: "SubscriptionError",
13964            completion_text: "SubscriptionError",
13965            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13966            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13967            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13968            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13969        },
13970        Run {
13971            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13972            initial_state: "Suˇscrirr".into(),
13973            buffer_marked_text: "<Su|scrirr>".into(),
13974            completion_label: "SubscriptionError",
13975            completion_text: "SubscriptionError",
13976            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13977            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13978            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13979            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13980        },
13981        Run {
13982            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13983            initial_state: "foo(indˇix)".into(),
13984            buffer_marked_text: "foo(<ind|ix>)".into(),
13985            completion_label: "node_index",
13986            completion_text: "node_index",
13987            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13988            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13989            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13990            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13991        },
13992        Run {
13993            run_description: "Replace range ends before cursor - should extend to cursor",
13994            initial_state: "before editˇo after".into(),
13995            buffer_marked_text: "before <{ed}>it|o after".into(),
13996            completion_label: "editor",
13997            completion_text: "editor",
13998            expected_with_insert_mode: "before editorˇo after".into(),
13999            expected_with_replace_mode: "before editorˇo after".into(),
14000            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14001            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14002        },
14003        Run {
14004            run_description: "Uses label for suffix matching",
14005            initial_state: "before ediˇtor after".into(),
14006            buffer_marked_text: "before <edi|tor> after".into(),
14007            completion_label: "editor",
14008            completion_text: "editor()",
14009            expected_with_insert_mode: "before editor()ˇtor after".into(),
14010            expected_with_replace_mode: "before editor()ˇ after".into(),
14011            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14012            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14013        },
14014        Run {
14015            run_description: "Case insensitive subsequence and suffix matching",
14016            initial_state: "before EDiˇtoR after".into(),
14017            buffer_marked_text: "before <EDi|toR> after".into(),
14018            completion_label: "editor",
14019            completion_text: "editor",
14020            expected_with_insert_mode: "before editorˇtoR after".into(),
14021            expected_with_replace_mode: "before editorˇ after".into(),
14022            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14023            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14024        },
14025    ];
14026
14027    for run in runs {
14028        let run_variations = [
14029            (LspInsertMode::Insert, run.expected_with_insert_mode),
14030            (LspInsertMode::Replace, run.expected_with_replace_mode),
14031            (
14032                LspInsertMode::ReplaceSubsequence,
14033                run.expected_with_replace_subsequence_mode,
14034            ),
14035            (
14036                LspInsertMode::ReplaceSuffix,
14037                run.expected_with_replace_suffix_mode,
14038            ),
14039        ];
14040
14041        for (lsp_insert_mode, expected_text) in run_variations {
14042            eprintln!(
14043                "run = {:?}, mode = {lsp_insert_mode:.?}",
14044                run.run_description,
14045            );
14046
14047            update_test_language_settings(&mut cx, |settings| {
14048                settings.defaults.completions = Some(CompletionSettingsContent {
14049                    lsp_insert_mode: Some(lsp_insert_mode),
14050                    words: Some(WordsCompletionMode::Disabled),
14051                    words_min_length: Some(0),
14052                    ..Default::default()
14053                });
14054            });
14055
14056            cx.set_state(&run.initial_state);
14057            cx.update_editor(|editor, window, cx| {
14058                editor.show_completions(&ShowCompletions, window, cx);
14059            });
14060
14061            let counter = Arc::new(AtomicUsize::new(0));
14062            handle_completion_request_with_insert_and_replace(
14063                &mut cx,
14064                &run.buffer_marked_text,
14065                vec![(run.completion_label, run.completion_text)],
14066                counter.clone(),
14067            )
14068            .await;
14069            cx.condition(|editor, _| editor.context_menu_visible())
14070                .await;
14071            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14072
14073            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14074                editor
14075                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14076                    .unwrap()
14077            });
14078            cx.assert_editor_state(&expected_text);
14079            handle_resolve_completion_request(&mut cx, None).await;
14080            apply_additional_edits.await.unwrap();
14081        }
14082    }
14083}
14084
14085#[gpui::test]
14086async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14087    init_test(cx, |_| {});
14088    let mut cx = EditorLspTestContext::new_rust(
14089        lsp::ServerCapabilities {
14090            completion_provider: Some(lsp::CompletionOptions {
14091                resolve_provider: Some(true),
14092                ..Default::default()
14093            }),
14094            ..Default::default()
14095        },
14096        cx,
14097    )
14098    .await;
14099
14100    let initial_state = "SubˇError";
14101    let buffer_marked_text = "<Sub|Error>";
14102    let completion_text = "SubscriptionError";
14103    let expected_with_insert_mode = "SubscriptionErrorˇError";
14104    let expected_with_replace_mode = "SubscriptionErrorˇ";
14105
14106    update_test_language_settings(&mut cx, |settings| {
14107        settings.defaults.completions = Some(CompletionSettingsContent {
14108            words: Some(WordsCompletionMode::Disabled),
14109            words_min_length: Some(0),
14110            // set the opposite here to ensure that the action is overriding the default behavior
14111            lsp_insert_mode: Some(LspInsertMode::Insert),
14112            ..Default::default()
14113        });
14114    });
14115
14116    cx.set_state(initial_state);
14117    cx.update_editor(|editor, window, cx| {
14118        editor.show_completions(&ShowCompletions, window, cx);
14119    });
14120
14121    let counter = Arc::new(AtomicUsize::new(0));
14122    handle_completion_request_with_insert_and_replace(
14123        &mut cx,
14124        buffer_marked_text,
14125        vec![(completion_text, completion_text)],
14126        counter.clone(),
14127    )
14128    .await;
14129    cx.condition(|editor, _| editor.context_menu_visible())
14130        .await;
14131    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14132
14133    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14134        editor
14135            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14136            .unwrap()
14137    });
14138    cx.assert_editor_state(expected_with_replace_mode);
14139    handle_resolve_completion_request(&mut cx, None).await;
14140    apply_additional_edits.await.unwrap();
14141
14142    update_test_language_settings(&mut cx, |settings| {
14143        settings.defaults.completions = Some(CompletionSettingsContent {
14144            words: Some(WordsCompletionMode::Disabled),
14145            words_min_length: Some(0),
14146            // set the opposite here to ensure that the action is overriding the default behavior
14147            lsp_insert_mode: Some(LspInsertMode::Replace),
14148            ..Default::default()
14149        });
14150    });
14151
14152    cx.set_state(initial_state);
14153    cx.update_editor(|editor, window, cx| {
14154        editor.show_completions(&ShowCompletions, window, cx);
14155    });
14156    handle_completion_request_with_insert_and_replace(
14157        &mut cx,
14158        buffer_marked_text,
14159        vec![(completion_text, completion_text)],
14160        counter.clone(),
14161    )
14162    .await;
14163    cx.condition(|editor, _| editor.context_menu_visible())
14164        .await;
14165    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14166
14167    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14168        editor
14169            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14170            .unwrap()
14171    });
14172    cx.assert_editor_state(expected_with_insert_mode);
14173    handle_resolve_completion_request(&mut cx, None).await;
14174    apply_additional_edits.await.unwrap();
14175}
14176
14177#[gpui::test]
14178async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14179    init_test(cx, |_| {});
14180    let mut cx = EditorLspTestContext::new_rust(
14181        lsp::ServerCapabilities {
14182            completion_provider: Some(lsp::CompletionOptions {
14183                resolve_provider: Some(true),
14184                ..Default::default()
14185            }),
14186            ..Default::default()
14187        },
14188        cx,
14189    )
14190    .await;
14191
14192    // scenario: surrounding text matches completion text
14193    let completion_text = "to_offset";
14194    let initial_state = indoc! {"
14195        1. buf.to_offˇsuffix
14196        2. buf.to_offˇsuf
14197        3. buf.to_offˇfix
14198        4. buf.to_offˇ
14199        5. into_offˇensive
14200        6. ˇsuffix
14201        7. let ˇ //
14202        8. aaˇzz
14203        9. buf.to_off«zzzzzˇ»suffix
14204        10. buf.«ˇzzzzz»suffix
14205        11. to_off«ˇzzzzz»
14206
14207        buf.to_offˇsuffix  // newest cursor
14208    "};
14209    let completion_marked_buffer = indoc! {"
14210        1. buf.to_offsuffix
14211        2. buf.to_offsuf
14212        3. buf.to_offfix
14213        4. buf.to_off
14214        5. into_offensive
14215        6. suffix
14216        7. let  //
14217        8. aazz
14218        9. buf.to_offzzzzzsuffix
14219        10. buf.zzzzzsuffix
14220        11. to_offzzzzz
14221
14222        buf.<to_off|suffix>  // newest cursor
14223    "};
14224    let expected = indoc! {"
14225        1. buf.to_offsetˇ
14226        2. buf.to_offsetˇsuf
14227        3. buf.to_offsetˇfix
14228        4. buf.to_offsetˇ
14229        5. into_offsetˇensive
14230        6. to_offsetˇsuffix
14231        7. let to_offsetˇ //
14232        8. aato_offsetˇzz
14233        9. buf.to_offsetˇ
14234        10. buf.to_offsetˇsuffix
14235        11. to_offsetˇ
14236
14237        buf.to_offsetˇ  // newest cursor
14238    "};
14239    cx.set_state(initial_state);
14240    cx.update_editor(|editor, window, cx| {
14241        editor.show_completions(&ShowCompletions, window, cx);
14242    });
14243    handle_completion_request_with_insert_and_replace(
14244        &mut cx,
14245        completion_marked_buffer,
14246        vec![(completion_text, completion_text)],
14247        Arc::new(AtomicUsize::new(0)),
14248    )
14249    .await;
14250    cx.condition(|editor, _| editor.context_menu_visible())
14251        .await;
14252    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14253        editor
14254            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14255            .unwrap()
14256    });
14257    cx.assert_editor_state(expected);
14258    handle_resolve_completion_request(&mut cx, None).await;
14259    apply_additional_edits.await.unwrap();
14260
14261    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14262    let completion_text = "foo_and_bar";
14263    let initial_state = indoc! {"
14264        1. ooanbˇ
14265        2. zooanbˇ
14266        3. ooanbˇz
14267        4. zooanbˇz
14268        5. ooanˇ
14269        6. oanbˇ
14270
14271        ooanbˇ
14272    "};
14273    let completion_marked_buffer = indoc! {"
14274        1. ooanb
14275        2. zooanb
14276        3. ooanbz
14277        4. zooanbz
14278        5. ooan
14279        6. oanb
14280
14281        <ooanb|>
14282    "};
14283    let expected = indoc! {"
14284        1. foo_and_barˇ
14285        2. zfoo_and_barˇ
14286        3. foo_and_barˇz
14287        4. zfoo_and_barˇz
14288        5. ooanfoo_and_barˇ
14289        6. oanbfoo_and_barˇ
14290
14291        foo_and_barˇ
14292    "};
14293    cx.set_state(initial_state);
14294    cx.update_editor(|editor, window, cx| {
14295        editor.show_completions(&ShowCompletions, window, cx);
14296    });
14297    handle_completion_request_with_insert_and_replace(
14298        &mut cx,
14299        completion_marked_buffer,
14300        vec![(completion_text, completion_text)],
14301        Arc::new(AtomicUsize::new(0)),
14302    )
14303    .await;
14304    cx.condition(|editor, _| editor.context_menu_visible())
14305        .await;
14306    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14307        editor
14308            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14309            .unwrap()
14310    });
14311    cx.assert_editor_state(expected);
14312    handle_resolve_completion_request(&mut cx, None).await;
14313    apply_additional_edits.await.unwrap();
14314
14315    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14316    // (expects the same as if it was inserted at the end)
14317    let completion_text = "foo_and_bar";
14318    let initial_state = indoc! {"
14319        1. ooˇanb
14320        2. zooˇanb
14321        3. ooˇanbz
14322        4. zooˇanbz
14323
14324        ooˇanb
14325    "};
14326    let completion_marked_buffer = indoc! {"
14327        1. ooanb
14328        2. zooanb
14329        3. ooanbz
14330        4. zooanbz
14331
14332        <oo|anb>
14333    "};
14334    let expected = indoc! {"
14335        1. foo_and_barˇ
14336        2. zfoo_and_barˇ
14337        3. foo_and_barˇz
14338        4. zfoo_and_barˇz
14339
14340        foo_and_barˇ
14341    "};
14342    cx.set_state(initial_state);
14343    cx.update_editor(|editor, window, cx| {
14344        editor.show_completions(&ShowCompletions, window, cx);
14345    });
14346    handle_completion_request_with_insert_and_replace(
14347        &mut cx,
14348        completion_marked_buffer,
14349        vec![(completion_text, completion_text)],
14350        Arc::new(AtomicUsize::new(0)),
14351    )
14352    .await;
14353    cx.condition(|editor, _| editor.context_menu_visible())
14354        .await;
14355    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14356        editor
14357            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14358            .unwrap()
14359    });
14360    cx.assert_editor_state(expected);
14361    handle_resolve_completion_request(&mut cx, None).await;
14362    apply_additional_edits.await.unwrap();
14363}
14364
14365// This used to crash
14366#[gpui::test]
14367async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14368    init_test(cx, |_| {});
14369
14370    let buffer_text = indoc! {"
14371        fn main() {
14372            10.satu;
14373
14374            //
14375            // separate cursors so they open in different excerpts (manually reproducible)
14376            //
14377
14378            10.satu20;
14379        }
14380    "};
14381    let multibuffer_text_with_selections = indoc! {"
14382        fn main() {
14383            10.satuˇ;
14384
14385            //
14386
14387            //
14388
14389            10.satuˇ20;
14390        }
14391    "};
14392    let expected_multibuffer = indoc! {"
14393        fn main() {
14394            10.saturating_sub()ˇ;
14395
14396            //
14397
14398            //
14399
14400            10.saturating_sub()ˇ;
14401        }
14402    "};
14403
14404    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14405    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14406
14407    let fs = FakeFs::new(cx.executor());
14408    fs.insert_tree(
14409        path!("/a"),
14410        json!({
14411            "main.rs": buffer_text,
14412        }),
14413    )
14414    .await;
14415
14416    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14417    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14418    language_registry.add(rust_lang());
14419    let mut fake_servers = language_registry.register_fake_lsp(
14420        "Rust",
14421        FakeLspAdapter {
14422            capabilities: lsp::ServerCapabilities {
14423                completion_provider: Some(lsp::CompletionOptions {
14424                    resolve_provider: None,
14425                    ..lsp::CompletionOptions::default()
14426                }),
14427                ..lsp::ServerCapabilities::default()
14428            },
14429            ..FakeLspAdapter::default()
14430        },
14431    );
14432    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14433    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14434    let buffer = project
14435        .update(cx, |project, cx| {
14436            project.open_local_buffer(path!("/a/main.rs"), cx)
14437        })
14438        .await
14439        .unwrap();
14440
14441    let multi_buffer = cx.new(|cx| {
14442        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14443        multi_buffer.push_excerpts(
14444            buffer.clone(),
14445            [ExcerptRange::new(0..first_excerpt_end)],
14446            cx,
14447        );
14448        multi_buffer.push_excerpts(
14449            buffer.clone(),
14450            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14451            cx,
14452        );
14453        multi_buffer
14454    });
14455
14456    let editor = workspace
14457        .update(cx, |_, window, cx| {
14458            cx.new(|cx| {
14459                Editor::new(
14460                    EditorMode::Full {
14461                        scale_ui_elements_with_buffer_font_size: false,
14462                        show_active_line_background: false,
14463                        sizing_behavior: SizingBehavior::Default,
14464                    },
14465                    multi_buffer.clone(),
14466                    Some(project.clone()),
14467                    window,
14468                    cx,
14469                )
14470            })
14471        })
14472        .unwrap();
14473
14474    let pane = workspace
14475        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14476        .unwrap();
14477    pane.update_in(cx, |pane, window, cx| {
14478        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14479    });
14480
14481    let fake_server = fake_servers.next().await.unwrap();
14482
14483    editor.update_in(cx, |editor, window, cx| {
14484        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14485            s.select_ranges([
14486                Point::new(1, 11)..Point::new(1, 11),
14487                Point::new(7, 11)..Point::new(7, 11),
14488            ])
14489        });
14490
14491        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14492    });
14493
14494    editor.update_in(cx, |editor, window, cx| {
14495        editor.show_completions(&ShowCompletions, window, cx);
14496    });
14497
14498    fake_server
14499        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14500            let completion_item = lsp::CompletionItem {
14501                label: "saturating_sub()".into(),
14502                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14503                    lsp::InsertReplaceEdit {
14504                        new_text: "saturating_sub()".to_owned(),
14505                        insert: lsp::Range::new(
14506                            lsp::Position::new(7, 7),
14507                            lsp::Position::new(7, 11),
14508                        ),
14509                        replace: lsp::Range::new(
14510                            lsp::Position::new(7, 7),
14511                            lsp::Position::new(7, 13),
14512                        ),
14513                    },
14514                )),
14515                ..lsp::CompletionItem::default()
14516            };
14517
14518            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14519        })
14520        .next()
14521        .await
14522        .unwrap();
14523
14524    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14525        .await;
14526
14527    editor
14528        .update_in(cx, |editor, window, cx| {
14529            editor
14530                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14531                .unwrap()
14532        })
14533        .await
14534        .unwrap();
14535
14536    editor.update(cx, |editor, cx| {
14537        assert_text_with_selections(editor, expected_multibuffer, cx);
14538    })
14539}
14540
14541#[gpui::test]
14542async fn test_completion(cx: &mut TestAppContext) {
14543    init_test(cx, |_| {});
14544
14545    let mut cx = EditorLspTestContext::new_rust(
14546        lsp::ServerCapabilities {
14547            completion_provider: Some(lsp::CompletionOptions {
14548                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14549                resolve_provider: Some(true),
14550                ..Default::default()
14551            }),
14552            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14553            ..Default::default()
14554        },
14555        cx,
14556    )
14557    .await;
14558    let counter = Arc::new(AtomicUsize::new(0));
14559
14560    cx.set_state(indoc! {"
14561        oneˇ
14562        two
14563        three
14564    "});
14565    cx.simulate_keystroke(".");
14566    handle_completion_request(
14567        indoc! {"
14568            one.|<>
14569            two
14570            three
14571        "},
14572        vec!["first_completion", "second_completion"],
14573        true,
14574        counter.clone(),
14575        &mut cx,
14576    )
14577    .await;
14578    cx.condition(|editor, _| editor.context_menu_visible())
14579        .await;
14580    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14581
14582    let _handler = handle_signature_help_request(
14583        &mut cx,
14584        lsp::SignatureHelp {
14585            signatures: vec![lsp::SignatureInformation {
14586                label: "test signature".to_string(),
14587                documentation: None,
14588                parameters: Some(vec![lsp::ParameterInformation {
14589                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14590                    documentation: None,
14591                }]),
14592                active_parameter: None,
14593            }],
14594            active_signature: None,
14595            active_parameter: None,
14596        },
14597    );
14598    cx.update_editor(|editor, window, cx| {
14599        assert!(
14600            !editor.signature_help_state.is_shown(),
14601            "No signature help was called for"
14602        );
14603        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14604    });
14605    cx.run_until_parked();
14606    cx.update_editor(|editor, _, _| {
14607        assert!(
14608            !editor.signature_help_state.is_shown(),
14609            "No signature help should be shown when completions menu is open"
14610        );
14611    });
14612
14613    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14614        editor.context_menu_next(&Default::default(), window, cx);
14615        editor
14616            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14617            .unwrap()
14618    });
14619    cx.assert_editor_state(indoc! {"
14620        one.second_completionˇ
14621        two
14622        three
14623    "});
14624
14625    handle_resolve_completion_request(
14626        &mut cx,
14627        Some(vec![
14628            (
14629                //This overlaps with the primary completion edit which is
14630                //misbehavior from the LSP spec, test that we filter it out
14631                indoc! {"
14632                    one.second_ˇcompletion
14633                    two
14634                    threeˇ
14635                "},
14636                "overlapping additional edit",
14637            ),
14638            (
14639                indoc! {"
14640                    one.second_completion
14641                    two
14642                    threeˇ
14643                "},
14644                "\nadditional edit",
14645            ),
14646        ]),
14647    )
14648    .await;
14649    apply_additional_edits.await.unwrap();
14650    cx.assert_editor_state(indoc! {"
14651        one.second_completionˇ
14652        two
14653        three
14654        additional edit
14655    "});
14656
14657    cx.set_state(indoc! {"
14658        one.second_completion
14659        twoˇ
14660        threeˇ
14661        additional edit
14662    "});
14663    cx.simulate_keystroke(" ");
14664    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14665    cx.simulate_keystroke("s");
14666    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14667
14668    cx.assert_editor_state(indoc! {"
14669        one.second_completion
14670        two sˇ
14671        three sˇ
14672        additional edit
14673    "});
14674    handle_completion_request(
14675        indoc! {"
14676            one.second_completion
14677            two s
14678            three <s|>
14679            additional edit
14680        "},
14681        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14682        true,
14683        counter.clone(),
14684        &mut cx,
14685    )
14686    .await;
14687    cx.condition(|editor, _| editor.context_menu_visible())
14688        .await;
14689    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14690
14691    cx.simulate_keystroke("i");
14692
14693    handle_completion_request(
14694        indoc! {"
14695            one.second_completion
14696            two si
14697            three <si|>
14698            additional edit
14699        "},
14700        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14701        true,
14702        counter.clone(),
14703        &mut cx,
14704    )
14705    .await;
14706    cx.condition(|editor, _| editor.context_menu_visible())
14707        .await;
14708    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14709
14710    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14711        editor
14712            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14713            .unwrap()
14714    });
14715    cx.assert_editor_state(indoc! {"
14716        one.second_completion
14717        two sixth_completionˇ
14718        three sixth_completionˇ
14719        additional edit
14720    "});
14721
14722    apply_additional_edits.await.unwrap();
14723
14724    update_test_language_settings(&mut cx, |settings| {
14725        settings.defaults.show_completions_on_input = Some(false);
14726    });
14727    cx.set_state("editorˇ");
14728    cx.simulate_keystroke(".");
14729    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14730    cx.simulate_keystrokes("c l o");
14731    cx.assert_editor_state("editor.cloˇ");
14732    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14733    cx.update_editor(|editor, window, cx| {
14734        editor.show_completions(&ShowCompletions, window, cx);
14735    });
14736    handle_completion_request(
14737        "editor.<clo|>",
14738        vec!["close", "clobber"],
14739        true,
14740        counter.clone(),
14741        &mut cx,
14742    )
14743    .await;
14744    cx.condition(|editor, _| editor.context_menu_visible())
14745        .await;
14746    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14747
14748    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14749        editor
14750            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14751            .unwrap()
14752    });
14753    cx.assert_editor_state("editor.clobberˇ");
14754    handle_resolve_completion_request(&mut cx, None).await;
14755    apply_additional_edits.await.unwrap();
14756}
14757
14758#[gpui::test]
14759async fn test_completion_can_run_commands(cx: &mut TestAppContext) {
14760    init_test(cx, |_| {});
14761
14762    let fs = FakeFs::new(cx.executor());
14763    fs.insert_tree(
14764        path!("/a"),
14765        json!({
14766            "main.rs": "",
14767        }),
14768    )
14769    .await;
14770
14771    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14772    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14773    language_registry.add(rust_lang());
14774    let command_calls = Arc::new(AtomicUsize::new(0));
14775    let registered_command = "_the/command";
14776
14777    let closure_command_calls = command_calls.clone();
14778    let mut fake_servers = language_registry.register_fake_lsp(
14779        "Rust",
14780        FakeLspAdapter {
14781            capabilities: lsp::ServerCapabilities {
14782                completion_provider: Some(lsp::CompletionOptions {
14783                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14784                    ..lsp::CompletionOptions::default()
14785                }),
14786                execute_command_provider: Some(lsp::ExecuteCommandOptions {
14787                    commands: vec![registered_command.to_owned()],
14788                    ..lsp::ExecuteCommandOptions::default()
14789                }),
14790                ..lsp::ServerCapabilities::default()
14791            },
14792            initializer: Some(Box::new(move |fake_server| {
14793                fake_server.set_request_handler::<lsp::request::Completion, _, _>(
14794                    move |params, _| async move {
14795                        Ok(Some(lsp::CompletionResponse::Array(vec![
14796                            lsp::CompletionItem {
14797                                label: "registered_command".to_owned(),
14798                                text_edit: gen_text_edit(&params, ""),
14799                                command: Some(lsp::Command {
14800                                    title: registered_command.to_owned(),
14801                                    command: "_the/command".to_owned(),
14802                                    arguments: Some(vec![serde_json::Value::Bool(true)]),
14803                                }),
14804                                ..lsp::CompletionItem::default()
14805                            },
14806                            lsp::CompletionItem {
14807                                label: "unregistered_command".to_owned(),
14808                                text_edit: gen_text_edit(&params, ""),
14809                                command: Some(lsp::Command {
14810                                    title: "????????????".to_owned(),
14811                                    command: "????????????".to_owned(),
14812                                    arguments: Some(vec![serde_json::Value::Null]),
14813                                }),
14814                                ..lsp::CompletionItem::default()
14815                            },
14816                        ])))
14817                    },
14818                );
14819                fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
14820                    let command_calls = closure_command_calls.clone();
14821                    move |params, _| {
14822                        assert_eq!(params.command, registered_command);
14823                        let command_calls = command_calls.clone();
14824                        async move {
14825                            command_calls.fetch_add(1, atomic::Ordering::Release);
14826                            Ok(Some(json!(null)))
14827                        }
14828                    }
14829                });
14830            })),
14831            ..FakeLspAdapter::default()
14832        },
14833    );
14834    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14835    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14836    let editor = workspace
14837        .update(cx, |workspace, window, cx| {
14838            workspace.open_abs_path(
14839                PathBuf::from(path!("/a/main.rs")),
14840                OpenOptions::default(),
14841                window,
14842                cx,
14843            )
14844        })
14845        .unwrap()
14846        .await
14847        .unwrap()
14848        .downcast::<Editor>()
14849        .unwrap();
14850    let _fake_server = fake_servers.next().await.unwrap();
14851
14852    editor.update_in(cx, |editor, window, cx| {
14853        cx.focus_self(window);
14854        editor.move_to_end(&MoveToEnd, window, cx);
14855        editor.handle_input(".", window, cx);
14856    });
14857    cx.run_until_parked();
14858    editor.update(cx, |editor, _| {
14859        assert!(editor.context_menu_visible());
14860        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14861        {
14862            let completion_labels = menu
14863                .completions
14864                .borrow()
14865                .iter()
14866                .map(|c| c.label.text.clone())
14867                .collect::<Vec<_>>();
14868            assert_eq!(
14869                completion_labels,
14870                &["registered_command", "unregistered_command",],
14871            );
14872        } else {
14873            panic!("expected completion menu to be open");
14874        }
14875    });
14876
14877    editor
14878        .update_in(cx, |editor, window, cx| {
14879            editor
14880                .confirm_completion(&ConfirmCompletion::default(), window, cx)
14881                .unwrap()
14882        })
14883        .await
14884        .unwrap();
14885    cx.run_until_parked();
14886    assert_eq!(
14887        command_calls.load(atomic::Ordering::Acquire),
14888        1,
14889        "For completion with a registered command, Zed should send a command execution request",
14890    );
14891
14892    editor.update_in(cx, |editor, window, cx| {
14893        cx.focus_self(window);
14894        editor.handle_input(".", window, cx);
14895    });
14896    cx.run_until_parked();
14897    editor.update(cx, |editor, _| {
14898        assert!(editor.context_menu_visible());
14899        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14900        {
14901            let completion_labels = menu
14902                .completions
14903                .borrow()
14904                .iter()
14905                .map(|c| c.label.text.clone())
14906                .collect::<Vec<_>>();
14907            assert_eq!(
14908                completion_labels,
14909                &["registered_command", "unregistered_command",],
14910            );
14911        } else {
14912            panic!("expected completion menu to be open");
14913        }
14914    });
14915    editor
14916        .update_in(cx, |editor, window, cx| {
14917            editor.context_menu_next(&Default::default(), window, cx);
14918            editor
14919                .confirm_completion(&ConfirmCompletion::default(), window, cx)
14920                .unwrap()
14921        })
14922        .await
14923        .unwrap();
14924    cx.run_until_parked();
14925    assert_eq!(
14926        command_calls.load(atomic::Ordering::Acquire),
14927        1,
14928        "For completion with an unregistered command, Zed should not send a command execution request",
14929    );
14930}
14931
14932#[gpui::test]
14933async fn test_completion_reuse(cx: &mut TestAppContext) {
14934    init_test(cx, |_| {});
14935
14936    let mut cx = EditorLspTestContext::new_rust(
14937        lsp::ServerCapabilities {
14938            completion_provider: Some(lsp::CompletionOptions {
14939                trigger_characters: Some(vec![".".to_string()]),
14940                ..Default::default()
14941            }),
14942            ..Default::default()
14943        },
14944        cx,
14945    )
14946    .await;
14947
14948    let counter = Arc::new(AtomicUsize::new(0));
14949    cx.set_state("objˇ");
14950    cx.simulate_keystroke(".");
14951
14952    // Initial completion request returns complete results
14953    let is_incomplete = false;
14954    handle_completion_request(
14955        "obj.|<>",
14956        vec!["a", "ab", "abc"],
14957        is_incomplete,
14958        counter.clone(),
14959        &mut cx,
14960    )
14961    .await;
14962    cx.run_until_parked();
14963    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14964    cx.assert_editor_state("obj.ˇ");
14965    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14966
14967    // Type "a" - filters existing completions
14968    cx.simulate_keystroke("a");
14969    cx.run_until_parked();
14970    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14971    cx.assert_editor_state("obj.aˇ");
14972    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14973
14974    // Type "b" - filters existing completions
14975    cx.simulate_keystroke("b");
14976    cx.run_until_parked();
14977    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14978    cx.assert_editor_state("obj.abˇ");
14979    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14980
14981    // Type "c" - filters existing completions
14982    cx.simulate_keystroke("c");
14983    cx.run_until_parked();
14984    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14985    cx.assert_editor_state("obj.abcˇ");
14986    check_displayed_completions(vec!["abc"], &mut cx);
14987
14988    // Backspace to delete "c" - filters existing completions
14989    cx.update_editor(|editor, window, cx| {
14990        editor.backspace(&Backspace, window, cx);
14991    });
14992    cx.run_until_parked();
14993    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14994    cx.assert_editor_state("obj.abˇ");
14995    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14996
14997    // Moving cursor to the left dismisses menu.
14998    cx.update_editor(|editor, window, cx| {
14999        editor.move_left(&MoveLeft, window, cx);
15000    });
15001    cx.run_until_parked();
15002    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15003    cx.assert_editor_state("obj.aˇb");
15004    cx.update_editor(|editor, _, _| {
15005        assert_eq!(editor.context_menu_visible(), false);
15006    });
15007
15008    // Type "b" - new request
15009    cx.simulate_keystroke("b");
15010    let is_incomplete = false;
15011    handle_completion_request(
15012        "obj.<ab|>a",
15013        vec!["ab", "abc"],
15014        is_incomplete,
15015        counter.clone(),
15016        &mut cx,
15017    )
15018    .await;
15019    cx.run_until_parked();
15020    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
15021    cx.assert_editor_state("obj.abˇb");
15022    check_displayed_completions(vec!["ab", "abc"], &mut cx);
15023
15024    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
15025    cx.update_editor(|editor, window, cx| {
15026        editor.backspace(&Backspace, window, cx);
15027    });
15028    let is_incomplete = false;
15029    handle_completion_request(
15030        "obj.<a|>b",
15031        vec!["a", "ab", "abc"],
15032        is_incomplete,
15033        counter.clone(),
15034        &mut cx,
15035    )
15036    .await;
15037    cx.run_until_parked();
15038    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15039    cx.assert_editor_state("obj.aˇb");
15040    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
15041
15042    // Backspace to delete "a" - dismisses menu.
15043    cx.update_editor(|editor, window, cx| {
15044        editor.backspace(&Backspace, window, cx);
15045    });
15046    cx.run_until_parked();
15047    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
15048    cx.assert_editor_state("obj.ˇb");
15049    cx.update_editor(|editor, _, _| {
15050        assert_eq!(editor.context_menu_visible(), false);
15051    });
15052}
15053
15054#[gpui::test]
15055async fn test_word_completion(cx: &mut TestAppContext) {
15056    let lsp_fetch_timeout_ms = 10;
15057    init_test(cx, |language_settings| {
15058        language_settings.defaults.completions = Some(CompletionSettingsContent {
15059            words_min_length: Some(0),
15060            lsp_fetch_timeout_ms: Some(10),
15061            lsp_insert_mode: Some(LspInsertMode::Insert),
15062            ..Default::default()
15063        });
15064    });
15065
15066    let mut cx = EditorLspTestContext::new_rust(
15067        lsp::ServerCapabilities {
15068            completion_provider: Some(lsp::CompletionOptions {
15069                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15070                ..lsp::CompletionOptions::default()
15071            }),
15072            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15073            ..lsp::ServerCapabilities::default()
15074        },
15075        cx,
15076    )
15077    .await;
15078
15079    let throttle_completions = Arc::new(AtomicBool::new(false));
15080
15081    let lsp_throttle_completions = throttle_completions.clone();
15082    let _completion_requests_handler =
15083        cx.lsp
15084            .server
15085            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
15086                let lsp_throttle_completions = lsp_throttle_completions.clone();
15087                let cx = cx.clone();
15088                async move {
15089                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
15090                        cx.background_executor()
15091                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
15092                            .await;
15093                    }
15094                    Ok(Some(lsp::CompletionResponse::Array(vec![
15095                        lsp::CompletionItem {
15096                            label: "first".into(),
15097                            ..lsp::CompletionItem::default()
15098                        },
15099                        lsp::CompletionItem {
15100                            label: "last".into(),
15101                            ..lsp::CompletionItem::default()
15102                        },
15103                    ])))
15104                }
15105            });
15106
15107    cx.set_state(indoc! {"
15108        oneˇ
15109        two
15110        three
15111    "});
15112    cx.simulate_keystroke(".");
15113    cx.executor().run_until_parked();
15114    cx.condition(|editor, _| editor.context_menu_visible())
15115        .await;
15116    cx.update_editor(|editor, window, cx| {
15117        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15118        {
15119            assert_eq!(
15120                completion_menu_entries(menu),
15121                &["first", "last"],
15122                "When LSP server is fast to reply, no fallback word completions are used"
15123            );
15124        } else {
15125            panic!("expected completion menu to be open");
15126        }
15127        editor.cancel(&Cancel, window, cx);
15128    });
15129    cx.executor().run_until_parked();
15130    cx.condition(|editor, _| !editor.context_menu_visible())
15131        .await;
15132
15133    throttle_completions.store(true, atomic::Ordering::Release);
15134    cx.simulate_keystroke(".");
15135    cx.executor()
15136        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
15137    cx.executor().run_until_parked();
15138    cx.condition(|editor, _| editor.context_menu_visible())
15139        .await;
15140    cx.update_editor(|editor, _, _| {
15141        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15142        {
15143            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
15144                "When LSP server is slow, document words can be shown instead, if configured accordingly");
15145        } else {
15146            panic!("expected completion menu to be open");
15147        }
15148    });
15149}
15150
15151#[gpui::test]
15152async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
15153    init_test(cx, |language_settings| {
15154        language_settings.defaults.completions = Some(CompletionSettingsContent {
15155            words: Some(WordsCompletionMode::Enabled),
15156            words_min_length: Some(0),
15157            lsp_insert_mode: Some(LspInsertMode::Insert),
15158            ..Default::default()
15159        });
15160    });
15161
15162    let mut cx = EditorLspTestContext::new_rust(
15163        lsp::ServerCapabilities {
15164            completion_provider: Some(lsp::CompletionOptions {
15165                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15166                ..lsp::CompletionOptions::default()
15167            }),
15168            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15169            ..lsp::ServerCapabilities::default()
15170        },
15171        cx,
15172    )
15173    .await;
15174
15175    let _completion_requests_handler =
15176        cx.lsp
15177            .server
15178            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15179                Ok(Some(lsp::CompletionResponse::Array(vec![
15180                    lsp::CompletionItem {
15181                        label: "first".into(),
15182                        ..lsp::CompletionItem::default()
15183                    },
15184                    lsp::CompletionItem {
15185                        label: "last".into(),
15186                        ..lsp::CompletionItem::default()
15187                    },
15188                ])))
15189            });
15190
15191    cx.set_state(indoc! {"ˇ
15192        first
15193        last
15194        second
15195    "});
15196    cx.simulate_keystroke(".");
15197    cx.executor().run_until_parked();
15198    cx.condition(|editor, _| editor.context_menu_visible())
15199        .await;
15200    cx.update_editor(|editor, _, _| {
15201        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15202        {
15203            assert_eq!(
15204                completion_menu_entries(menu),
15205                &["first", "last", "second"],
15206                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15207            );
15208        } else {
15209            panic!("expected completion menu to be open");
15210        }
15211    });
15212}
15213
15214#[gpui::test]
15215async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15216    init_test(cx, |language_settings| {
15217        language_settings.defaults.completions = Some(CompletionSettingsContent {
15218            words: Some(WordsCompletionMode::Disabled),
15219            words_min_length: Some(0),
15220            lsp_insert_mode: Some(LspInsertMode::Insert),
15221            ..Default::default()
15222        });
15223    });
15224
15225    let mut cx = EditorLspTestContext::new_rust(
15226        lsp::ServerCapabilities {
15227            completion_provider: Some(lsp::CompletionOptions {
15228                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15229                ..lsp::CompletionOptions::default()
15230            }),
15231            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15232            ..lsp::ServerCapabilities::default()
15233        },
15234        cx,
15235    )
15236    .await;
15237
15238    let _completion_requests_handler =
15239        cx.lsp
15240            .server
15241            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15242                panic!("LSP completions should not be queried when dealing with word completions")
15243            });
15244
15245    cx.set_state(indoc! {"ˇ
15246        first
15247        last
15248        second
15249    "});
15250    cx.update_editor(|editor, window, cx| {
15251        editor.show_word_completions(&ShowWordCompletions, window, cx);
15252    });
15253    cx.executor().run_until_parked();
15254    cx.condition(|editor, _| editor.context_menu_visible())
15255        .await;
15256    cx.update_editor(|editor, _, _| {
15257        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15258        {
15259            assert_eq!(
15260                completion_menu_entries(menu),
15261                &["first", "last", "second"],
15262                "`ShowWordCompletions` action should show word completions"
15263            );
15264        } else {
15265            panic!("expected completion menu to be open");
15266        }
15267    });
15268
15269    cx.simulate_keystroke("l");
15270    cx.executor().run_until_parked();
15271    cx.condition(|editor, _| editor.context_menu_visible())
15272        .await;
15273    cx.update_editor(|editor, _, _| {
15274        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15275        {
15276            assert_eq!(
15277                completion_menu_entries(menu),
15278                &["last"],
15279                "After showing word completions, further editing should filter them and not query the LSP"
15280            );
15281        } else {
15282            panic!("expected completion menu to be open");
15283        }
15284    });
15285}
15286
15287#[gpui::test]
15288async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15289    init_test(cx, |language_settings| {
15290        language_settings.defaults.completions = Some(CompletionSettingsContent {
15291            words_min_length: Some(0),
15292            lsp: Some(false),
15293            lsp_insert_mode: Some(LspInsertMode::Insert),
15294            ..Default::default()
15295        });
15296    });
15297
15298    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15299
15300    cx.set_state(indoc! {"ˇ
15301        0_usize
15302        let
15303        33
15304        4.5f32
15305    "});
15306    cx.update_editor(|editor, window, cx| {
15307        editor.show_completions(&ShowCompletions, window, cx);
15308    });
15309    cx.executor().run_until_parked();
15310    cx.condition(|editor, _| editor.context_menu_visible())
15311        .await;
15312    cx.update_editor(|editor, window, cx| {
15313        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15314        {
15315            assert_eq!(
15316                completion_menu_entries(menu),
15317                &["let"],
15318                "With no digits in the completion query, no digits should be in the word completions"
15319            );
15320        } else {
15321            panic!("expected completion menu to be open");
15322        }
15323        editor.cancel(&Cancel, window, cx);
15324    });
15325
15326    cx.set_state(indoc! {"15327        0_usize
15328        let
15329        3
15330        33.35f32
15331    "});
15332    cx.update_editor(|editor, window, cx| {
15333        editor.show_completions(&ShowCompletions, window, cx);
15334    });
15335    cx.executor().run_until_parked();
15336    cx.condition(|editor, _| editor.context_menu_visible())
15337        .await;
15338    cx.update_editor(|editor, _, _| {
15339        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15340        {
15341            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15342                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15343        } else {
15344            panic!("expected completion menu to be open");
15345        }
15346    });
15347}
15348
15349#[gpui::test]
15350async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15351    init_test(cx, |language_settings| {
15352        language_settings.defaults.completions = Some(CompletionSettingsContent {
15353            words: Some(WordsCompletionMode::Enabled),
15354            words_min_length: Some(3),
15355            lsp_insert_mode: Some(LspInsertMode::Insert),
15356            ..Default::default()
15357        });
15358    });
15359
15360    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15361    cx.set_state(indoc! {"ˇ
15362        wow
15363        wowen
15364        wowser
15365    "});
15366    cx.simulate_keystroke("w");
15367    cx.executor().run_until_parked();
15368    cx.update_editor(|editor, _, _| {
15369        if editor.context_menu.borrow_mut().is_some() {
15370            panic!(
15371                "expected completion menu to be hidden, as words completion threshold is not met"
15372            );
15373        }
15374    });
15375
15376    cx.update_editor(|editor, window, cx| {
15377        editor.show_word_completions(&ShowWordCompletions, window, cx);
15378    });
15379    cx.executor().run_until_parked();
15380    cx.update_editor(|editor, window, cx| {
15381        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15382        {
15383            assert_eq!(completion_menu_entries(menu), &["wowser", "wowen", "wow"], "Even though the threshold is not met, invoking word completions with an action should provide the completions");
15384        } else {
15385            panic!("expected completion menu to be open after the word completions are called with an action");
15386        }
15387
15388        editor.cancel(&Cancel, window, cx);
15389    });
15390    cx.update_editor(|editor, _, _| {
15391        if editor.context_menu.borrow_mut().is_some() {
15392            panic!("expected completion menu to be hidden after canceling");
15393        }
15394    });
15395
15396    cx.simulate_keystroke("o");
15397    cx.executor().run_until_parked();
15398    cx.update_editor(|editor, _, _| {
15399        if editor.context_menu.borrow_mut().is_some() {
15400            panic!(
15401                "expected completion menu to be hidden, as words completion threshold is not met still"
15402            );
15403        }
15404    });
15405
15406    cx.simulate_keystroke("w");
15407    cx.executor().run_until_parked();
15408    cx.update_editor(|editor, _, _| {
15409        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15410        {
15411            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15412        } else {
15413            panic!("expected completion menu to be open after the word completions threshold is met");
15414        }
15415    });
15416}
15417
15418#[gpui::test]
15419async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15420    init_test(cx, |language_settings| {
15421        language_settings.defaults.completions = Some(CompletionSettingsContent {
15422            words: Some(WordsCompletionMode::Enabled),
15423            words_min_length: Some(0),
15424            lsp_insert_mode: Some(LspInsertMode::Insert),
15425            ..Default::default()
15426        });
15427    });
15428
15429    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15430    cx.update_editor(|editor, _, _| {
15431        editor.disable_word_completions();
15432    });
15433    cx.set_state(indoc! {"ˇ
15434        wow
15435        wowen
15436        wowser
15437    "});
15438    cx.simulate_keystroke("w");
15439    cx.executor().run_until_parked();
15440    cx.update_editor(|editor, _, _| {
15441        if editor.context_menu.borrow_mut().is_some() {
15442            panic!(
15443                "expected completion menu to be hidden, as words completion are disabled for this editor"
15444            );
15445        }
15446    });
15447
15448    cx.update_editor(|editor, window, cx| {
15449        editor.show_word_completions(&ShowWordCompletions, window, cx);
15450    });
15451    cx.executor().run_until_parked();
15452    cx.update_editor(|editor, _, _| {
15453        if editor.context_menu.borrow_mut().is_some() {
15454            panic!(
15455                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15456            );
15457        }
15458    });
15459}
15460
15461#[gpui::test]
15462async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15463    init_test(cx, |language_settings| {
15464        language_settings.defaults.completions = Some(CompletionSettingsContent {
15465            words: Some(WordsCompletionMode::Disabled),
15466            words_min_length: Some(0),
15467            lsp_insert_mode: Some(LspInsertMode::Insert),
15468            ..Default::default()
15469        });
15470    });
15471
15472    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15473    cx.update_editor(|editor, _, _| {
15474        editor.set_completion_provider(None);
15475    });
15476    cx.set_state(indoc! {"ˇ
15477        wow
15478        wowen
15479        wowser
15480    "});
15481    cx.simulate_keystroke("w");
15482    cx.executor().run_until_parked();
15483    cx.update_editor(|editor, _, _| {
15484        if editor.context_menu.borrow_mut().is_some() {
15485            panic!("expected completion menu to be hidden, as disabled in settings");
15486        }
15487    });
15488}
15489
15490fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15491    let position = || lsp::Position {
15492        line: params.text_document_position.position.line,
15493        character: params.text_document_position.position.character,
15494    };
15495    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15496        range: lsp::Range {
15497            start: position(),
15498            end: position(),
15499        },
15500        new_text: text.to_string(),
15501    }))
15502}
15503
15504#[gpui::test]
15505async fn test_multiline_completion(cx: &mut TestAppContext) {
15506    init_test(cx, |_| {});
15507
15508    let fs = FakeFs::new(cx.executor());
15509    fs.insert_tree(
15510        path!("/a"),
15511        json!({
15512            "main.ts": "a",
15513        }),
15514    )
15515    .await;
15516
15517    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15518    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15519    let typescript_language = Arc::new(Language::new(
15520        LanguageConfig {
15521            name: "TypeScript".into(),
15522            matcher: LanguageMatcher {
15523                path_suffixes: vec!["ts".to_string()],
15524                ..LanguageMatcher::default()
15525            },
15526            line_comments: vec!["// ".into()],
15527            ..LanguageConfig::default()
15528        },
15529        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15530    ));
15531    language_registry.add(typescript_language.clone());
15532    let mut fake_servers = language_registry.register_fake_lsp(
15533        "TypeScript",
15534        FakeLspAdapter {
15535            capabilities: lsp::ServerCapabilities {
15536                completion_provider: Some(lsp::CompletionOptions {
15537                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15538                    ..lsp::CompletionOptions::default()
15539                }),
15540                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15541                ..lsp::ServerCapabilities::default()
15542            },
15543            // Emulate vtsls label generation
15544            label_for_completion: Some(Box::new(|item, _| {
15545                let text = if let Some(description) = item
15546                    .label_details
15547                    .as_ref()
15548                    .and_then(|label_details| label_details.description.as_ref())
15549                {
15550                    format!("{} {}", item.label, description)
15551                } else if let Some(detail) = &item.detail {
15552                    format!("{} {}", item.label, detail)
15553                } else {
15554                    item.label.clone()
15555                };
15556                Some(language::CodeLabel::plain(text, None))
15557            })),
15558            ..FakeLspAdapter::default()
15559        },
15560    );
15561    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15562    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15563    let worktree_id = workspace
15564        .update(cx, |workspace, _window, cx| {
15565            workspace.project().update(cx, |project, cx| {
15566                project.worktrees(cx).next().unwrap().read(cx).id()
15567            })
15568        })
15569        .unwrap();
15570    let _buffer = project
15571        .update(cx, |project, cx| {
15572            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15573        })
15574        .await
15575        .unwrap();
15576    let editor = workspace
15577        .update(cx, |workspace, window, cx| {
15578            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15579        })
15580        .unwrap()
15581        .await
15582        .unwrap()
15583        .downcast::<Editor>()
15584        .unwrap();
15585    let fake_server = fake_servers.next().await.unwrap();
15586
15587    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15588    let multiline_label_2 = "a\nb\nc\n";
15589    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15590    let multiline_description = "d\ne\nf\n";
15591    let multiline_detail_2 = "g\nh\ni\n";
15592
15593    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15594        move |params, _| async move {
15595            Ok(Some(lsp::CompletionResponse::Array(vec![
15596                lsp::CompletionItem {
15597                    label: multiline_label.to_string(),
15598                    text_edit: gen_text_edit(&params, "new_text_1"),
15599                    ..lsp::CompletionItem::default()
15600                },
15601                lsp::CompletionItem {
15602                    label: "single line label 1".to_string(),
15603                    detail: Some(multiline_detail.to_string()),
15604                    text_edit: gen_text_edit(&params, "new_text_2"),
15605                    ..lsp::CompletionItem::default()
15606                },
15607                lsp::CompletionItem {
15608                    label: "single line label 2".to_string(),
15609                    label_details: Some(lsp::CompletionItemLabelDetails {
15610                        description: Some(multiline_description.to_string()),
15611                        detail: None,
15612                    }),
15613                    text_edit: gen_text_edit(&params, "new_text_2"),
15614                    ..lsp::CompletionItem::default()
15615                },
15616                lsp::CompletionItem {
15617                    label: multiline_label_2.to_string(),
15618                    detail: Some(multiline_detail_2.to_string()),
15619                    text_edit: gen_text_edit(&params, "new_text_3"),
15620                    ..lsp::CompletionItem::default()
15621                },
15622                lsp::CompletionItem {
15623                    label: "Label with many     spaces and \t but without newlines".to_string(),
15624                    detail: Some(
15625                        "Details with many     spaces and \t but without newlines".to_string(),
15626                    ),
15627                    text_edit: gen_text_edit(&params, "new_text_4"),
15628                    ..lsp::CompletionItem::default()
15629                },
15630            ])))
15631        },
15632    );
15633
15634    editor.update_in(cx, |editor, window, cx| {
15635        cx.focus_self(window);
15636        editor.move_to_end(&MoveToEnd, window, cx);
15637        editor.handle_input(".", window, cx);
15638    });
15639    cx.run_until_parked();
15640    completion_handle.next().await.unwrap();
15641
15642    editor.update(cx, |editor, _| {
15643        assert!(editor.context_menu_visible());
15644        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15645        {
15646            let completion_labels = menu
15647                .completions
15648                .borrow()
15649                .iter()
15650                .map(|c| c.label.text.clone())
15651                .collect::<Vec<_>>();
15652            assert_eq!(
15653                completion_labels,
15654                &[
15655                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15656                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15657                    "single line label 2 d e f ",
15658                    "a b c g h i ",
15659                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15660                ],
15661                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15662            );
15663
15664            for completion in menu
15665                .completions
15666                .borrow()
15667                .iter() {
15668                    assert_eq!(
15669                        completion.label.filter_range,
15670                        0..completion.label.text.len(),
15671                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15672                    );
15673                }
15674        } else {
15675            panic!("expected completion menu to be open");
15676        }
15677    });
15678}
15679
15680#[gpui::test]
15681async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15682    init_test(cx, |_| {});
15683    let mut cx = EditorLspTestContext::new_rust(
15684        lsp::ServerCapabilities {
15685            completion_provider: Some(lsp::CompletionOptions {
15686                trigger_characters: Some(vec![".".to_string()]),
15687                ..Default::default()
15688            }),
15689            ..Default::default()
15690        },
15691        cx,
15692    )
15693    .await;
15694    cx.lsp
15695        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15696            Ok(Some(lsp::CompletionResponse::Array(vec![
15697                lsp::CompletionItem {
15698                    label: "first".into(),
15699                    ..Default::default()
15700                },
15701                lsp::CompletionItem {
15702                    label: "last".into(),
15703                    ..Default::default()
15704                },
15705            ])))
15706        });
15707    cx.set_state("variableˇ");
15708    cx.simulate_keystroke(".");
15709    cx.executor().run_until_parked();
15710
15711    cx.update_editor(|editor, _, _| {
15712        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15713        {
15714            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15715        } else {
15716            panic!("expected completion menu to be open");
15717        }
15718    });
15719
15720    cx.update_editor(|editor, window, cx| {
15721        editor.move_page_down(&MovePageDown::default(), window, cx);
15722        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15723        {
15724            assert!(
15725                menu.selected_item == 1,
15726                "expected PageDown to select the last item from the context menu"
15727            );
15728        } else {
15729            panic!("expected completion menu to stay open after PageDown");
15730        }
15731    });
15732
15733    cx.update_editor(|editor, window, cx| {
15734        editor.move_page_up(&MovePageUp::default(), window, cx);
15735        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15736        {
15737            assert!(
15738                menu.selected_item == 0,
15739                "expected PageUp to select the first item from the context menu"
15740            );
15741        } else {
15742            panic!("expected completion menu to stay open after PageUp");
15743        }
15744    });
15745}
15746
15747#[gpui::test]
15748async fn test_as_is_completions(cx: &mut TestAppContext) {
15749    init_test(cx, |_| {});
15750    let mut cx = EditorLspTestContext::new_rust(
15751        lsp::ServerCapabilities {
15752            completion_provider: Some(lsp::CompletionOptions {
15753                ..Default::default()
15754            }),
15755            ..Default::default()
15756        },
15757        cx,
15758    )
15759    .await;
15760    cx.lsp
15761        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15762            Ok(Some(lsp::CompletionResponse::Array(vec![
15763                lsp::CompletionItem {
15764                    label: "unsafe".into(),
15765                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15766                        range: lsp::Range {
15767                            start: lsp::Position {
15768                                line: 1,
15769                                character: 2,
15770                            },
15771                            end: lsp::Position {
15772                                line: 1,
15773                                character: 3,
15774                            },
15775                        },
15776                        new_text: "unsafe".to_string(),
15777                    })),
15778                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15779                    ..Default::default()
15780                },
15781            ])))
15782        });
15783    cx.set_state("fn a() {}\n");
15784    cx.executor().run_until_parked();
15785    cx.update_editor(|editor, window, cx| {
15786        editor.trigger_completion_on_input("n", true, window, cx)
15787    });
15788    cx.executor().run_until_parked();
15789
15790    cx.update_editor(|editor, window, cx| {
15791        editor.confirm_completion(&Default::default(), window, cx)
15792    });
15793    cx.executor().run_until_parked();
15794    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15795}
15796
15797#[gpui::test]
15798async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15799    init_test(cx, |_| {});
15800    let language =
15801        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15802    let mut cx = EditorLspTestContext::new(
15803        language,
15804        lsp::ServerCapabilities {
15805            completion_provider: Some(lsp::CompletionOptions {
15806                ..lsp::CompletionOptions::default()
15807            }),
15808            ..lsp::ServerCapabilities::default()
15809        },
15810        cx,
15811    )
15812    .await;
15813
15814    cx.set_state(
15815        "#ifndef BAR_H
15816#define BAR_H
15817
15818#include <stdbool.h>
15819
15820int fn_branch(bool do_branch1, bool do_branch2);
15821
15822#endif // BAR_H
15823ˇ",
15824    );
15825    cx.executor().run_until_parked();
15826    cx.update_editor(|editor, window, cx| {
15827        editor.handle_input("#", window, cx);
15828    });
15829    cx.executor().run_until_parked();
15830    cx.update_editor(|editor, window, cx| {
15831        editor.handle_input("i", window, cx);
15832    });
15833    cx.executor().run_until_parked();
15834    cx.update_editor(|editor, window, cx| {
15835        editor.handle_input("n", window, cx);
15836    });
15837    cx.executor().run_until_parked();
15838    cx.assert_editor_state(
15839        "#ifndef BAR_H
15840#define BAR_H
15841
15842#include <stdbool.h>
15843
15844int fn_branch(bool do_branch1, bool do_branch2);
15845
15846#endif // BAR_H
15847#inˇ",
15848    );
15849
15850    cx.lsp
15851        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15852            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15853                is_incomplete: false,
15854                item_defaults: None,
15855                items: vec![lsp::CompletionItem {
15856                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15857                    label_details: Some(lsp::CompletionItemLabelDetails {
15858                        detail: Some("header".to_string()),
15859                        description: None,
15860                    }),
15861                    label: " include".to_string(),
15862                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15863                        range: lsp::Range {
15864                            start: lsp::Position {
15865                                line: 8,
15866                                character: 1,
15867                            },
15868                            end: lsp::Position {
15869                                line: 8,
15870                                character: 1,
15871                            },
15872                        },
15873                        new_text: "include \"$0\"".to_string(),
15874                    })),
15875                    sort_text: Some("40b67681include".to_string()),
15876                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15877                    filter_text: Some("include".to_string()),
15878                    insert_text: Some("include \"$0\"".to_string()),
15879                    ..lsp::CompletionItem::default()
15880                }],
15881            })))
15882        });
15883    cx.update_editor(|editor, window, cx| {
15884        editor.show_completions(&ShowCompletions, window, cx);
15885    });
15886    cx.executor().run_until_parked();
15887    cx.update_editor(|editor, window, cx| {
15888        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15889    });
15890    cx.executor().run_until_parked();
15891    cx.assert_editor_state(
15892        "#ifndef BAR_H
15893#define BAR_H
15894
15895#include <stdbool.h>
15896
15897int fn_branch(bool do_branch1, bool do_branch2);
15898
15899#endif // BAR_H
15900#include \"ˇ\"",
15901    );
15902
15903    cx.lsp
15904        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15905            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15906                is_incomplete: true,
15907                item_defaults: None,
15908                items: vec![lsp::CompletionItem {
15909                    kind: Some(lsp::CompletionItemKind::FILE),
15910                    label: "AGL/".to_string(),
15911                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15912                        range: lsp::Range {
15913                            start: lsp::Position {
15914                                line: 8,
15915                                character: 10,
15916                            },
15917                            end: lsp::Position {
15918                                line: 8,
15919                                character: 11,
15920                            },
15921                        },
15922                        new_text: "AGL/".to_string(),
15923                    })),
15924                    sort_text: Some("40b67681AGL/".to_string()),
15925                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15926                    filter_text: Some("AGL/".to_string()),
15927                    insert_text: Some("AGL/".to_string()),
15928                    ..lsp::CompletionItem::default()
15929                }],
15930            })))
15931        });
15932    cx.update_editor(|editor, window, cx| {
15933        editor.show_completions(&ShowCompletions, window, cx);
15934    });
15935    cx.executor().run_until_parked();
15936    cx.update_editor(|editor, window, cx| {
15937        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15938    });
15939    cx.executor().run_until_parked();
15940    cx.assert_editor_state(
15941        r##"#ifndef BAR_H
15942#define BAR_H
15943
15944#include <stdbool.h>
15945
15946int fn_branch(bool do_branch1, bool do_branch2);
15947
15948#endif // BAR_H
15949#include "AGL/ˇ"##,
15950    );
15951
15952    cx.update_editor(|editor, window, cx| {
15953        editor.handle_input("\"", window, cx);
15954    });
15955    cx.executor().run_until_parked();
15956    cx.assert_editor_state(
15957        r##"#ifndef BAR_H
15958#define BAR_H
15959
15960#include <stdbool.h>
15961
15962int fn_branch(bool do_branch1, bool do_branch2);
15963
15964#endif // BAR_H
15965#include "AGL/"ˇ"##,
15966    );
15967}
15968
15969#[gpui::test]
15970async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15971    init_test(cx, |_| {});
15972
15973    let mut cx = EditorLspTestContext::new_rust(
15974        lsp::ServerCapabilities {
15975            completion_provider: Some(lsp::CompletionOptions {
15976                trigger_characters: Some(vec![".".to_string()]),
15977                resolve_provider: Some(true),
15978                ..Default::default()
15979            }),
15980            ..Default::default()
15981        },
15982        cx,
15983    )
15984    .await;
15985
15986    cx.set_state("fn main() { let a = 2ˇ; }");
15987    cx.simulate_keystroke(".");
15988    let completion_item = lsp::CompletionItem {
15989        label: "Some".into(),
15990        kind: Some(lsp::CompletionItemKind::SNIPPET),
15991        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15992        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15993            kind: lsp::MarkupKind::Markdown,
15994            value: "```rust\nSome(2)\n```".to_string(),
15995        })),
15996        deprecated: Some(false),
15997        sort_text: Some("Some".to_string()),
15998        filter_text: Some("Some".to_string()),
15999        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
16000        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
16001            range: lsp::Range {
16002                start: lsp::Position {
16003                    line: 0,
16004                    character: 22,
16005                },
16006                end: lsp::Position {
16007                    line: 0,
16008                    character: 22,
16009                },
16010            },
16011            new_text: "Some(2)".to_string(),
16012        })),
16013        additional_text_edits: Some(vec![lsp::TextEdit {
16014            range: lsp::Range {
16015                start: lsp::Position {
16016                    line: 0,
16017                    character: 20,
16018                },
16019                end: lsp::Position {
16020                    line: 0,
16021                    character: 22,
16022                },
16023            },
16024            new_text: "".to_string(),
16025        }]),
16026        ..Default::default()
16027    };
16028
16029    let closure_completion_item = completion_item.clone();
16030    let counter = Arc::new(AtomicUsize::new(0));
16031    let counter_clone = counter.clone();
16032    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
16033        let task_completion_item = closure_completion_item.clone();
16034        counter_clone.fetch_add(1, atomic::Ordering::Release);
16035        async move {
16036            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
16037                is_incomplete: true,
16038                item_defaults: None,
16039                items: vec![task_completion_item],
16040            })))
16041        }
16042    });
16043
16044    cx.condition(|editor, _| editor.context_menu_visible())
16045        .await;
16046    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
16047    assert!(request.next().await.is_some());
16048    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
16049
16050    cx.simulate_keystrokes("S o m");
16051    cx.condition(|editor, _| editor.context_menu_visible())
16052        .await;
16053    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
16054    assert!(request.next().await.is_some());
16055    assert!(request.next().await.is_some());
16056    assert!(request.next().await.is_some());
16057    request.close();
16058    assert!(request.next().await.is_none());
16059    assert_eq!(
16060        counter.load(atomic::Ordering::Acquire),
16061        4,
16062        "With the completions menu open, only one LSP request should happen per input"
16063    );
16064}
16065
16066#[gpui::test]
16067async fn test_toggle_comment(cx: &mut TestAppContext) {
16068    init_test(cx, |_| {});
16069    let mut cx = EditorTestContext::new(cx).await;
16070    let language = Arc::new(Language::new(
16071        LanguageConfig {
16072            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16073            ..Default::default()
16074        },
16075        Some(tree_sitter_rust::LANGUAGE.into()),
16076    ));
16077    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16078
16079    // If multiple selections intersect a line, the line is only toggled once.
16080    cx.set_state(indoc! {"
16081        fn a() {
16082            «//b();
16083            ˇ»// «c();
16084            //ˇ»  d();
16085        }
16086    "});
16087
16088    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16089
16090    cx.assert_editor_state(indoc! {"
16091        fn a() {
16092            «b();
16093            c();
16094            ˇ» d();
16095        }
16096    "});
16097
16098    // The comment prefix is inserted at the same column for every line in a
16099    // selection.
16100    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16101
16102    cx.assert_editor_state(indoc! {"
16103        fn a() {
16104            // «b();
16105            // c();
16106            ˇ»//  d();
16107        }
16108    "});
16109
16110    // If a selection ends at the beginning of a line, that line is not toggled.
16111    cx.set_selections_state(indoc! {"
16112        fn a() {
16113            // b();
16114            «// c();
16115        ˇ»    //  d();
16116        }
16117    "});
16118
16119    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16120
16121    cx.assert_editor_state(indoc! {"
16122        fn a() {
16123            // b();
16124            «c();
16125        ˇ»    //  d();
16126        }
16127    "});
16128
16129    // If a selection span a single line and is empty, the line is toggled.
16130    cx.set_state(indoc! {"
16131        fn a() {
16132            a();
16133            b();
16134        ˇ
16135        }
16136    "});
16137
16138    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16139
16140    cx.assert_editor_state(indoc! {"
16141        fn a() {
16142            a();
16143            b();
16144        //•ˇ
16145        }
16146    "});
16147
16148    // If a selection span multiple lines, empty lines are not toggled.
16149    cx.set_state(indoc! {"
16150        fn a() {
16151            «a();
16152
16153            c();ˇ»
16154        }
16155    "});
16156
16157    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16158
16159    cx.assert_editor_state(indoc! {"
16160        fn a() {
16161            // «a();
16162
16163            // c();ˇ»
16164        }
16165    "});
16166
16167    // If a selection includes multiple comment prefixes, all lines are uncommented.
16168    cx.set_state(indoc! {"
16169        fn a() {
16170            «// a();
16171            /// b();
16172            //! c();ˇ»
16173        }
16174    "});
16175
16176    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16177
16178    cx.assert_editor_state(indoc! {"
16179        fn a() {
16180            «a();
16181            b();
16182            c();ˇ»
16183        }
16184    "});
16185}
16186
16187#[gpui::test]
16188async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16189    init_test(cx, |_| {});
16190    let mut cx = EditorTestContext::new(cx).await;
16191    let language = Arc::new(Language::new(
16192        LanguageConfig {
16193            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16194            ..Default::default()
16195        },
16196        Some(tree_sitter_rust::LANGUAGE.into()),
16197    ));
16198    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
16199
16200    let toggle_comments = &ToggleComments {
16201        advance_downwards: false,
16202        ignore_indent: true,
16203    };
16204
16205    // If multiple selections intersect a line, the line is only toggled once.
16206    cx.set_state(indoc! {"
16207        fn a() {
16208        //    «b();
16209        //    c();
16210        //    ˇ» d();
16211        }
16212    "});
16213
16214    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16215
16216    cx.assert_editor_state(indoc! {"
16217        fn a() {
16218            «b();
16219            c();
16220            ˇ» d();
16221        }
16222    "});
16223
16224    // The comment prefix is inserted at the beginning of each line
16225    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16226
16227    cx.assert_editor_state(indoc! {"
16228        fn a() {
16229        //    «b();
16230        //    c();
16231        //    ˇ» d();
16232        }
16233    "});
16234
16235    // If a selection ends at the beginning of a line, that line is not toggled.
16236    cx.set_selections_state(indoc! {"
16237        fn a() {
16238        //    b();
16239        //    «c();
16240        ˇ»//     d();
16241        }
16242    "});
16243
16244    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16245
16246    cx.assert_editor_state(indoc! {"
16247        fn a() {
16248        //    b();
16249            «c();
16250        ˇ»//     d();
16251        }
16252    "});
16253
16254    // If a selection span a single line and is empty, the line is toggled.
16255    cx.set_state(indoc! {"
16256        fn a() {
16257            a();
16258            b();
16259        ˇ
16260        }
16261    "});
16262
16263    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16264
16265    cx.assert_editor_state(indoc! {"
16266        fn a() {
16267            a();
16268            b();
16269        //ˇ
16270        }
16271    "});
16272
16273    // If a selection span multiple lines, empty lines are not toggled.
16274    cx.set_state(indoc! {"
16275        fn a() {
16276            «a();
16277
16278            c();ˇ»
16279        }
16280    "});
16281
16282    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16283
16284    cx.assert_editor_state(indoc! {"
16285        fn a() {
16286        //    «a();
16287
16288        //    c();ˇ»
16289        }
16290    "});
16291
16292    // If a selection includes multiple comment prefixes, all lines are uncommented.
16293    cx.set_state(indoc! {"
16294        fn a() {
16295        //    «a();
16296        ///    b();
16297        //!    c();ˇ»
16298        }
16299    "});
16300
16301    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16302
16303    cx.assert_editor_state(indoc! {"
16304        fn a() {
16305            «a();
16306            b();
16307            c();ˇ»
16308        }
16309    "});
16310}
16311
16312#[gpui::test]
16313async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16314    init_test(cx, |_| {});
16315
16316    let language = Arc::new(Language::new(
16317        LanguageConfig {
16318            line_comments: vec!["// ".into()],
16319            ..Default::default()
16320        },
16321        Some(tree_sitter_rust::LANGUAGE.into()),
16322    ));
16323
16324    let mut cx = EditorTestContext::new(cx).await;
16325
16326    cx.language_registry().add(language.clone());
16327    cx.update_buffer(|buffer, cx| {
16328        buffer.set_language(Some(language), cx);
16329    });
16330
16331    let toggle_comments = &ToggleComments {
16332        advance_downwards: true,
16333        ignore_indent: false,
16334    };
16335
16336    // Single cursor on one line -> advance
16337    // Cursor moves horizontally 3 characters as well on non-blank line
16338    cx.set_state(indoc!(
16339        "fn a() {
16340             ˇdog();
16341             cat();
16342        }"
16343    ));
16344    cx.update_editor(|editor, window, cx| {
16345        editor.toggle_comments(toggle_comments, window, cx);
16346    });
16347    cx.assert_editor_state(indoc!(
16348        "fn a() {
16349             // dog();
16350             catˇ();
16351        }"
16352    ));
16353
16354    // Single selection on one line -> don't advance
16355    cx.set_state(indoc!(
16356        "fn a() {
16357             «dog()ˇ»;
16358             cat();
16359        }"
16360    ));
16361    cx.update_editor(|editor, window, cx| {
16362        editor.toggle_comments(toggle_comments, window, cx);
16363    });
16364    cx.assert_editor_state(indoc!(
16365        "fn a() {
16366             // «dog()ˇ»;
16367             cat();
16368        }"
16369    ));
16370
16371    // Multiple cursors on one line -> advance
16372    cx.set_state(indoc!(
16373        "fn a() {
16374             ˇdˇog();
16375             cat();
16376        }"
16377    ));
16378    cx.update_editor(|editor, window, cx| {
16379        editor.toggle_comments(toggle_comments, window, cx);
16380    });
16381    cx.assert_editor_state(indoc!(
16382        "fn a() {
16383             // dog();
16384             catˇ(ˇ);
16385        }"
16386    ));
16387
16388    // Multiple cursors on one line, with selection -> don't advance
16389    cx.set_state(indoc!(
16390        "fn a() {
16391             ˇdˇog«()ˇ»;
16392             cat();
16393        }"
16394    ));
16395    cx.update_editor(|editor, window, cx| {
16396        editor.toggle_comments(toggle_comments, window, cx);
16397    });
16398    cx.assert_editor_state(indoc!(
16399        "fn a() {
16400             // ˇdˇog«()ˇ»;
16401             cat();
16402        }"
16403    ));
16404
16405    // Single cursor on one line -> advance
16406    // Cursor moves to column 0 on blank line
16407    cx.set_state(indoc!(
16408        "fn a() {
16409             ˇdog();
16410
16411             cat();
16412        }"
16413    ));
16414    cx.update_editor(|editor, window, cx| {
16415        editor.toggle_comments(toggle_comments, window, cx);
16416    });
16417    cx.assert_editor_state(indoc!(
16418        "fn a() {
16419             // dog();
16420        ˇ
16421             cat();
16422        }"
16423    ));
16424
16425    // Single cursor on one line -> advance
16426    // Cursor starts and ends at column 0
16427    cx.set_state(indoc!(
16428        "fn a() {
16429         ˇ    dog();
16430             cat();
16431        }"
16432    ));
16433    cx.update_editor(|editor, window, cx| {
16434        editor.toggle_comments(toggle_comments, window, cx);
16435    });
16436    cx.assert_editor_state(indoc!(
16437        "fn a() {
16438             // dog();
16439         ˇ    cat();
16440        }"
16441    ));
16442}
16443
16444#[gpui::test]
16445async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16446    init_test(cx, |_| {});
16447
16448    let mut cx = EditorTestContext::new(cx).await;
16449
16450    let html_language = Arc::new(
16451        Language::new(
16452            LanguageConfig {
16453                name: "HTML".into(),
16454                block_comment: Some(BlockCommentConfig {
16455                    start: "<!-- ".into(),
16456                    prefix: "".into(),
16457                    end: " -->".into(),
16458                    tab_size: 0,
16459                }),
16460                ..Default::default()
16461            },
16462            Some(tree_sitter_html::LANGUAGE.into()),
16463        )
16464        .with_injection_query(
16465            r#"
16466            (script_element
16467                (raw_text) @injection.content
16468                (#set! injection.language "javascript"))
16469            "#,
16470        )
16471        .unwrap(),
16472    );
16473
16474    let javascript_language = Arc::new(Language::new(
16475        LanguageConfig {
16476            name: "JavaScript".into(),
16477            line_comments: vec!["// ".into()],
16478            ..Default::default()
16479        },
16480        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16481    ));
16482
16483    cx.language_registry().add(html_language.clone());
16484    cx.language_registry().add(javascript_language);
16485    cx.update_buffer(|buffer, cx| {
16486        buffer.set_language(Some(html_language), cx);
16487    });
16488
16489    // Toggle comments for empty selections
16490    cx.set_state(
16491        &r#"
16492            <p>A</p>ˇ
16493            <p>B</p>ˇ
16494            <p>C</p>ˇ
16495        "#
16496        .unindent(),
16497    );
16498    cx.update_editor(|editor, window, cx| {
16499        editor.toggle_comments(&ToggleComments::default(), window, cx)
16500    });
16501    cx.assert_editor_state(
16502        &r#"
16503            <!-- <p>A</p>ˇ -->
16504            <!-- <p>B</p>ˇ -->
16505            <!-- <p>C</p>ˇ -->
16506        "#
16507        .unindent(),
16508    );
16509    cx.update_editor(|editor, window, cx| {
16510        editor.toggle_comments(&ToggleComments::default(), window, cx)
16511    });
16512    cx.assert_editor_state(
16513        &r#"
16514            <p>A</p>ˇ
16515            <p>B</p>ˇ
16516            <p>C</p>ˇ
16517        "#
16518        .unindent(),
16519    );
16520
16521    // Toggle comments for mixture of empty and non-empty selections, where
16522    // multiple selections occupy a given line.
16523    cx.set_state(
16524        &r#"
16525            <p>A«</p>
16526            <p>ˇ»B</p>ˇ
16527            <p>C«</p>
16528            <p>ˇ»D</p>ˇ
16529        "#
16530        .unindent(),
16531    );
16532
16533    cx.update_editor(|editor, window, cx| {
16534        editor.toggle_comments(&ToggleComments::default(), window, cx)
16535    });
16536    cx.assert_editor_state(
16537        &r#"
16538            <!-- <p>A«</p>
16539            <p>ˇ»B</p>ˇ -->
16540            <!-- <p>C«</p>
16541            <p>ˇ»D</p>ˇ -->
16542        "#
16543        .unindent(),
16544    );
16545    cx.update_editor(|editor, window, cx| {
16546        editor.toggle_comments(&ToggleComments::default(), window, cx)
16547    });
16548    cx.assert_editor_state(
16549        &r#"
16550            <p>A«</p>
16551            <p>ˇ»B</p>ˇ
16552            <p>C«</p>
16553            <p>ˇ»D</p>ˇ
16554        "#
16555        .unindent(),
16556    );
16557
16558    // Toggle comments when different languages are active for different
16559    // selections.
16560    cx.set_state(
16561        &r#"
16562            ˇ<script>
16563                ˇvar x = new Y();
16564            ˇ</script>
16565        "#
16566        .unindent(),
16567    );
16568    cx.executor().run_until_parked();
16569    cx.update_editor(|editor, window, cx| {
16570        editor.toggle_comments(&ToggleComments::default(), window, cx)
16571    });
16572    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16573    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16574    cx.assert_editor_state(
16575        &r#"
16576            <!-- ˇ<script> -->
16577                // ˇvar x = new Y();
16578            <!-- ˇ</script> -->
16579        "#
16580        .unindent(),
16581    );
16582}
16583
16584#[gpui::test]
16585fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16586    init_test(cx, |_| {});
16587
16588    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16589    let multibuffer = cx.new(|cx| {
16590        let mut multibuffer = MultiBuffer::new(ReadWrite);
16591        multibuffer.push_excerpts(
16592            buffer.clone(),
16593            [
16594                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16595                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16596            ],
16597            cx,
16598        );
16599        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16600        multibuffer
16601    });
16602
16603    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16604    editor.update_in(cx, |editor, window, cx| {
16605        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16606        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16607            s.select_ranges([
16608                Point::new(0, 0)..Point::new(0, 0),
16609                Point::new(1, 0)..Point::new(1, 0),
16610            ])
16611        });
16612
16613        editor.handle_input("X", window, cx);
16614        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16615        assert_eq!(
16616            editor.selections.ranges(&editor.display_snapshot(cx)),
16617            [
16618                Point::new(0, 1)..Point::new(0, 1),
16619                Point::new(1, 1)..Point::new(1, 1),
16620            ]
16621        );
16622
16623        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16624        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16625            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16626        });
16627        editor.backspace(&Default::default(), window, cx);
16628        assert_eq!(editor.text(cx), "Xa\nbbb");
16629        assert_eq!(
16630            editor.selections.ranges(&editor.display_snapshot(cx)),
16631            [Point::new(1, 0)..Point::new(1, 0)]
16632        );
16633
16634        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16635            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16636        });
16637        editor.backspace(&Default::default(), window, cx);
16638        assert_eq!(editor.text(cx), "X\nbb");
16639        assert_eq!(
16640            editor.selections.ranges(&editor.display_snapshot(cx)),
16641            [Point::new(0, 1)..Point::new(0, 1)]
16642        );
16643    });
16644}
16645
16646#[gpui::test]
16647fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16648    init_test(cx, |_| {});
16649
16650    let markers = vec![('[', ']').into(), ('(', ')').into()];
16651    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16652        indoc! {"
16653            [aaaa
16654            (bbbb]
16655            cccc)",
16656        },
16657        markers.clone(),
16658    );
16659    let excerpt_ranges = markers.into_iter().map(|marker| {
16660        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16661        ExcerptRange::new(context)
16662    });
16663    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16664    let multibuffer = cx.new(|cx| {
16665        let mut multibuffer = MultiBuffer::new(ReadWrite);
16666        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16667        multibuffer
16668    });
16669
16670    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16671    editor.update_in(cx, |editor, window, cx| {
16672        let (expected_text, selection_ranges) = marked_text_ranges(
16673            indoc! {"
16674                aaaa
16675                bˇbbb
16676                bˇbbˇb
16677                cccc"
16678            },
16679            true,
16680        );
16681        assert_eq!(editor.text(cx), expected_text);
16682        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16683            s.select_ranges(
16684                selection_ranges
16685                    .iter()
16686                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16687            )
16688        });
16689
16690        editor.handle_input("X", window, cx);
16691
16692        let (expected_text, expected_selections) = marked_text_ranges(
16693            indoc! {"
16694                aaaa
16695                bXˇbbXb
16696                bXˇbbXˇb
16697                cccc"
16698            },
16699            false,
16700        );
16701        assert_eq!(editor.text(cx), expected_text);
16702        assert_eq!(
16703            editor.selections.ranges(&editor.display_snapshot(cx)),
16704            expected_selections
16705                .iter()
16706                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16707                .collect::<Vec<_>>()
16708        );
16709
16710        editor.newline(&Newline, window, cx);
16711        let (expected_text, expected_selections) = marked_text_ranges(
16712            indoc! {"
16713                aaaa
16714                bX
16715                ˇbbX
16716                b
16717                bX
16718                ˇbbX
16719                ˇb
16720                cccc"
16721            },
16722            false,
16723        );
16724        assert_eq!(editor.text(cx), expected_text);
16725        assert_eq!(
16726            editor.selections.ranges(&editor.display_snapshot(cx)),
16727            expected_selections
16728                .iter()
16729                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16730                .collect::<Vec<_>>()
16731        );
16732    });
16733}
16734
16735#[gpui::test]
16736fn test_refresh_selections(cx: &mut TestAppContext) {
16737    init_test(cx, |_| {});
16738
16739    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16740    let mut excerpt1_id = None;
16741    let multibuffer = cx.new(|cx| {
16742        let mut multibuffer = MultiBuffer::new(ReadWrite);
16743        excerpt1_id = multibuffer
16744            .push_excerpts(
16745                buffer.clone(),
16746                [
16747                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16748                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16749                ],
16750                cx,
16751            )
16752            .into_iter()
16753            .next();
16754        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16755        multibuffer
16756    });
16757
16758    let editor = cx.add_window(|window, cx| {
16759        let mut editor = build_editor(multibuffer.clone(), window, cx);
16760        let snapshot = editor.snapshot(window, cx);
16761        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16762            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16763        });
16764        editor.begin_selection(
16765            Point::new(2, 1).to_display_point(&snapshot),
16766            true,
16767            1,
16768            window,
16769            cx,
16770        );
16771        assert_eq!(
16772            editor.selections.ranges(&editor.display_snapshot(cx)),
16773            [
16774                Point::new(1, 3)..Point::new(1, 3),
16775                Point::new(2, 1)..Point::new(2, 1),
16776            ]
16777        );
16778        editor
16779    });
16780
16781    // Refreshing selections is a no-op when excerpts haven't changed.
16782    _ = editor.update(cx, |editor, window, cx| {
16783        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16784        assert_eq!(
16785            editor.selections.ranges(&editor.display_snapshot(cx)),
16786            [
16787                Point::new(1, 3)..Point::new(1, 3),
16788                Point::new(2, 1)..Point::new(2, 1),
16789            ]
16790        );
16791    });
16792
16793    multibuffer.update(cx, |multibuffer, cx| {
16794        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16795    });
16796    _ = editor.update(cx, |editor, window, cx| {
16797        // Removing an excerpt causes the first selection to become degenerate.
16798        assert_eq!(
16799            editor.selections.ranges(&editor.display_snapshot(cx)),
16800            [
16801                Point::new(0, 0)..Point::new(0, 0),
16802                Point::new(0, 1)..Point::new(0, 1)
16803            ]
16804        );
16805
16806        // Refreshing selections will relocate the first selection to the original buffer
16807        // location.
16808        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16809        assert_eq!(
16810            editor.selections.ranges(&editor.display_snapshot(cx)),
16811            [
16812                Point::new(0, 1)..Point::new(0, 1),
16813                Point::new(0, 3)..Point::new(0, 3)
16814            ]
16815        );
16816        assert!(editor.selections.pending_anchor().is_some());
16817    });
16818}
16819
16820#[gpui::test]
16821fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16822    init_test(cx, |_| {});
16823
16824    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16825    let mut excerpt1_id = None;
16826    let multibuffer = cx.new(|cx| {
16827        let mut multibuffer = MultiBuffer::new(ReadWrite);
16828        excerpt1_id = multibuffer
16829            .push_excerpts(
16830                buffer.clone(),
16831                [
16832                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16833                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16834                ],
16835                cx,
16836            )
16837            .into_iter()
16838            .next();
16839        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16840        multibuffer
16841    });
16842
16843    let editor = cx.add_window(|window, cx| {
16844        let mut editor = build_editor(multibuffer.clone(), window, cx);
16845        let snapshot = editor.snapshot(window, cx);
16846        editor.begin_selection(
16847            Point::new(1, 3).to_display_point(&snapshot),
16848            false,
16849            1,
16850            window,
16851            cx,
16852        );
16853        assert_eq!(
16854            editor.selections.ranges(&editor.display_snapshot(cx)),
16855            [Point::new(1, 3)..Point::new(1, 3)]
16856        );
16857        editor
16858    });
16859
16860    multibuffer.update(cx, |multibuffer, cx| {
16861        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16862    });
16863    _ = editor.update(cx, |editor, window, cx| {
16864        assert_eq!(
16865            editor.selections.ranges(&editor.display_snapshot(cx)),
16866            [Point::new(0, 0)..Point::new(0, 0)]
16867        );
16868
16869        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16871        assert_eq!(
16872            editor.selections.ranges(&editor.display_snapshot(cx)),
16873            [Point::new(0, 3)..Point::new(0, 3)]
16874        );
16875        assert!(editor.selections.pending_anchor().is_some());
16876    });
16877}
16878
16879#[gpui::test]
16880async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16881    init_test(cx, |_| {});
16882
16883    let language = Arc::new(
16884        Language::new(
16885            LanguageConfig {
16886                brackets: BracketPairConfig {
16887                    pairs: vec![
16888                        BracketPair {
16889                            start: "{".to_string(),
16890                            end: "}".to_string(),
16891                            close: true,
16892                            surround: true,
16893                            newline: true,
16894                        },
16895                        BracketPair {
16896                            start: "/* ".to_string(),
16897                            end: " */".to_string(),
16898                            close: true,
16899                            surround: true,
16900                            newline: true,
16901                        },
16902                    ],
16903                    ..Default::default()
16904                },
16905                ..Default::default()
16906            },
16907            Some(tree_sitter_rust::LANGUAGE.into()),
16908        )
16909        .with_indents_query("")
16910        .unwrap(),
16911    );
16912
16913    let text = concat!(
16914        "{   }\n",     //
16915        "  x\n",       //
16916        "  /*   */\n", //
16917        "x\n",         //
16918        "{{} }\n",     //
16919    );
16920
16921    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
16922    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16923    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16924    editor
16925        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16926        .await;
16927
16928    editor.update_in(cx, |editor, window, cx| {
16929        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16930            s.select_display_ranges([
16931                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16932                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16933                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16934            ])
16935        });
16936        editor.newline(&Newline, window, cx);
16937
16938        assert_eq!(
16939            editor.buffer().read(cx).read(cx).text(),
16940            concat!(
16941                "{ \n",    // Suppress rustfmt
16942                "\n",      //
16943                "}\n",     //
16944                "  x\n",   //
16945                "  /* \n", //
16946                "  \n",    //
16947                "  */\n",  //
16948                "x\n",     //
16949                "{{} \n",  //
16950                "}\n",     //
16951            )
16952        );
16953    });
16954}
16955
16956#[gpui::test]
16957fn test_highlighted_ranges(cx: &mut TestAppContext) {
16958    init_test(cx, |_| {});
16959
16960    let editor = cx.add_window(|window, cx| {
16961        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16962        build_editor(buffer, window, cx)
16963    });
16964
16965    _ = editor.update(cx, |editor, window, cx| {
16966        struct Type1;
16967        struct Type2;
16968
16969        let buffer = editor.buffer.read(cx).snapshot(cx);
16970
16971        let anchor_range =
16972            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16973
16974        editor.highlight_background::<Type1>(
16975            &[
16976                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16977                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16978                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16979                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16980            ],
16981            |_, _| Hsla::red(),
16982            cx,
16983        );
16984        editor.highlight_background::<Type2>(
16985            &[
16986                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16987                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16988                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16989                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16990            ],
16991            |_, _| Hsla::green(),
16992            cx,
16993        );
16994
16995        let snapshot = editor.snapshot(window, cx);
16996        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16997            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16998            &snapshot,
16999            cx.theme(),
17000        );
17001        assert_eq!(
17002            highlighted_ranges,
17003            &[
17004                (
17005                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
17006                    Hsla::green(),
17007                ),
17008                (
17009                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
17010                    Hsla::red(),
17011                ),
17012                (
17013                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
17014                    Hsla::green(),
17015                ),
17016                (
17017                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17018                    Hsla::red(),
17019                ),
17020            ]
17021        );
17022        assert_eq!(
17023            editor.sorted_background_highlights_in_range(
17024                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
17025                &snapshot,
17026                cx.theme(),
17027            ),
17028            &[(
17029                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
17030                Hsla::red(),
17031            )]
17032        );
17033    });
17034}
17035
17036#[gpui::test]
17037async fn test_following(cx: &mut TestAppContext) {
17038    init_test(cx, |_| {});
17039
17040    let fs = FakeFs::new(cx.executor());
17041    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17042
17043    let buffer = project.update(cx, |project, cx| {
17044        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
17045        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
17046    });
17047    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17048    let follower = cx.update(|cx| {
17049        cx.open_window(
17050            WindowOptions {
17051                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
17052                    gpui::Point::new(px(0.), px(0.)),
17053                    gpui::Point::new(px(10.), px(80.)),
17054                ))),
17055                ..Default::default()
17056            },
17057            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
17058        )
17059        .unwrap()
17060    });
17061
17062    let is_still_following = Rc::new(RefCell::new(true));
17063    let follower_edit_event_count = Rc::new(RefCell::new(0));
17064    let pending_update = Rc::new(RefCell::new(None));
17065    let leader_entity = leader.root(cx).unwrap();
17066    let follower_entity = follower.root(cx).unwrap();
17067    _ = follower.update(cx, {
17068        let update = pending_update.clone();
17069        let is_still_following = is_still_following.clone();
17070        let follower_edit_event_count = follower_edit_event_count.clone();
17071        |_, window, cx| {
17072            cx.subscribe_in(
17073                &leader_entity,
17074                window,
17075                move |_, leader, event, window, cx| {
17076                    leader.read(cx).add_event_to_update_proto(
17077                        event,
17078                        &mut update.borrow_mut(),
17079                        window,
17080                        cx,
17081                    );
17082                },
17083            )
17084            .detach();
17085
17086            cx.subscribe_in(
17087                &follower_entity,
17088                window,
17089                move |_, _, event: &EditorEvent, _window, _cx| {
17090                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
17091                        *is_still_following.borrow_mut() = false;
17092                    }
17093
17094                    if let EditorEvent::BufferEdited = event {
17095                        *follower_edit_event_count.borrow_mut() += 1;
17096                    }
17097                },
17098            )
17099            .detach();
17100        }
17101    });
17102
17103    // Update the selections only
17104    _ = leader.update(cx, |leader, window, cx| {
17105        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17106            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17107        });
17108    });
17109    follower
17110        .update(cx, |follower, window, cx| {
17111            follower.apply_update_proto(
17112                &project,
17113                pending_update.borrow_mut().take().unwrap(),
17114                window,
17115                cx,
17116            )
17117        })
17118        .unwrap()
17119        .await
17120        .unwrap();
17121    _ = follower.update(cx, |follower, _, cx| {
17122        assert_eq!(
17123            follower.selections.ranges(&follower.display_snapshot(cx)),
17124            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
17125        );
17126    });
17127    assert!(*is_still_following.borrow());
17128    assert_eq!(*follower_edit_event_count.borrow(), 0);
17129
17130    // Update the scroll position only
17131    _ = leader.update(cx, |leader, window, cx| {
17132        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17133    });
17134    follower
17135        .update(cx, |follower, window, cx| {
17136            follower.apply_update_proto(
17137                &project,
17138                pending_update.borrow_mut().take().unwrap(),
17139                window,
17140                cx,
17141            )
17142        })
17143        .unwrap()
17144        .await
17145        .unwrap();
17146    assert_eq!(
17147        follower
17148            .update(cx, |follower, _, cx| follower.scroll_position(cx))
17149            .unwrap(),
17150        gpui::Point::new(1.5, 3.5)
17151    );
17152    assert!(*is_still_following.borrow());
17153    assert_eq!(*follower_edit_event_count.borrow(), 0);
17154
17155    // Update the selections and scroll position. The follower's scroll position is updated
17156    // via autoscroll, not via the leader's exact scroll position.
17157    _ = leader.update(cx, |leader, window, cx| {
17158        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17159            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
17160        });
17161        leader.request_autoscroll(Autoscroll::newest(), cx);
17162        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
17163    });
17164    follower
17165        .update(cx, |follower, window, cx| {
17166            follower.apply_update_proto(
17167                &project,
17168                pending_update.borrow_mut().take().unwrap(),
17169                window,
17170                cx,
17171            )
17172        })
17173        .unwrap()
17174        .await
17175        .unwrap();
17176    _ = follower.update(cx, |follower, _, cx| {
17177        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17178        assert_eq!(
17179            follower.selections.ranges(&follower.display_snapshot(cx)),
17180            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17181        );
17182    });
17183    assert!(*is_still_following.borrow());
17184
17185    // Creating a pending selection that precedes another selection
17186    _ = leader.update(cx, |leader, window, cx| {
17187        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17188            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17189        });
17190        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17191    });
17192    follower
17193        .update(cx, |follower, window, cx| {
17194            follower.apply_update_proto(
17195                &project,
17196                pending_update.borrow_mut().take().unwrap(),
17197                window,
17198                cx,
17199            )
17200        })
17201        .unwrap()
17202        .await
17203        .unwrap();
17204    _ = follower.update(cx, |follower, _, cx| {
17205        assert_eq!(
17206            follower.selections.ranges(&follower.display_snapshot(cx)),
17207            vec![
17208                MultiBufferOffset(0)..MultiBufferOffset(0),
17209                MultiBufferOffset(1)..MultiBufferOffset(1)
17210            ]
17211        );
17212    });
17213    assert!(*is_still_following.borrow());
17214
17215    // Extend the pending selection so that it surrounds another selection
17216    _ = leader.update(cx, |leader, window, cx| {
17217        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17218    });
17219    follower
17220        .update(cx, |follower, window, cx| {
17221            follower.apply_update_proto(
17222                &project,
17223                pending_update.borrow_mut().take().unwrap(),
17224                window,
17225                cx,
17226            )
17227        })
17228        .unwrap()
17229        .await
17230        .unwrap();
17231    _ = follower.update(cx, |follower, _, cx| {
17232        assert_eq!(
17233            follower.selections.ranges(&follower.display_snapshot(cx)),
17234            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17235        );
17236    });
17237
17238    // Scrolling locally breaks the follow
17239    _ = follower.update(cx, |follower, window, cx| {
17240        let top_anchor = follower
17241            .buffer()
17242            .read(cx)
17243            .read(cx)
17244            .anchor_after(MultiBufferOffset(0));
17245        follower.set_scroll_anchor(
17246            ScrollAnchor {
17247                anchor: top_anchor,
17248                offset: gpui::Point::new(0.0, 0.5),
17249            },
17250            window,
17251            cx,
17252        );
17253    });
17254    assert!(!(*is_still_following.borrow()));
17255}
17256
17257#[gpui::test]
17258async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17259    init_test(cx, |_| {});
17260
17261    let fs = FakeFs::new(cx.executor());
17262    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17263    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17264    let pane = workspace
17265        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17266        .unwrap();
17267
17268    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17269
17270    let leader = pane.update_in(cx, |_, window, cx| {
17271        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17272        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17273    });
17274
17275    // Start following the editor when it has no excerpts.
17276    let mut state_message =
17277        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17278    let workspace_entity = workspace.root(cx).unwrap();
17279    let follower_1 = cx
17280        .update_window(*workspace.deref(), |_, window, cx| {
17281            Editor::from_state_proto(
17282                workspace_entity,
17283                ViewId {
17284                    creator: CollaboratorId::PeerId(PeerId::default()),
17285                    id: 0,
17286                },
17287                &mut state_message,
17288                window,
17289                cx,
17290            )
17291        })
17292        .unwrap()
17293        .unwrap()
17294        .await
17295        .unwrap();
17296
17297    let update_message = Rc::new(RefCell::new(None));
17298    follower_1.update_in(cx, {
17299        let update = update_message.clone();
17300        |_, window, cx| {
17301            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17302                leader.read(cx).add_event_to_update_proto(
17303                    event,
17304                    &mut update.borrow_mut(),
17305                    window,
17306                    cx,
17307                );
17308            })
17309            .detach();
17310        }
17311    });
17312
17313    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17314        (
17315            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17316            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17317        )
17318    });
17319
17320    // Insert some excerpts.
17321    leader.update(cx, |leader, cx| {
17322        leader.buffer.update(cx, |multibuffer, cx| {
17323            multibuffer.set_excerpts_for_path(
17324                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17325                buffer_1.clone(),
17326                vec![
17327                    Point::row_range(0..3),
17328                    Point::row_range(1..6),
17329                    Point::row_range(12..15),
17330                ],
17331                0,
17332                cx,
17333            );
17334            multibuffer.set_excerpts_for_path(
17335                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17336                buffer_2.clone(),
17337                vec![Point::row_range(0..6), Point::row_range(8..12)],
17338                0,
17339                cx,
17340            );
17341        });
17342    });
17343
17344    // Apply the update of adding the excerpts.
17345    follower_1
17346        .update_in(cx, |follower, window, cx| {
17347            follower.apply_update_proto(
17348                &project,
17349                update_message.borrow().clone().unwrap(),
17350                window,
17351                cx,
17352            )
17353        })
17354        .await
17355        .unwrap();
17356    assert_eq!(
17357        follower_1.update(cx, |editor, cx| editor.text(cx)),
17358        leader.update(cx, |editor, cx| editor.text(cx))
17359    );
17360    update_message.borrow_mut().take();
17361
17362    // Start following separately after it already has excerpts.
17363    let mut state_message =
17364        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17365    let workspace_entity = workspace.root(cx).unwrap();
17366    let follower_2 = cx
17367        .update_window(*workspace.deref(), |_, window, cx| {
17368            Editor::from_state_proto(
17369                workspace_entity,
17370                ViewId {
17371                    creator: CollaboratorId::PeerId(PeerId::default()),
17372                    id: 0,
17373                },
17374                &mut state_message,
17375                window,
17376                cx,
17377            )
17378        })
17379        .unwrap()
17380        .unwrap()
17381        .await
17382        .unwrap();
17383    assert_eq!(
17384        follower_2.update(cx, |editor, cx| editor.text(cx)),
17385        leader.update(cx, |editor, cx| editor.text(cx))
17386    );
17387
17388    // Remove some excerpts.
17389    leader.update(cx, |leader, cx| {
17390        leader.buffer.update(cx, |multibuffer, cx| {
17391            let excerpt_ids = multibuffer.excerpt_ids();
17392            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17393            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17394        });
17395    });
17396
17397    // Apply the update of removing the excerpts.
17398    follower_1
17399        .update_in(cx, |follower, window, cx| {
17400            follower.apply_update_proto(
17401                &project,
17402                update_message.borrow().clone().unwrap(),
17403                window,
17404                cx,
17405            )
17406        })
17407        .await
17408        .unwrap();
17409    follower_2
17410        .update_in(cx, |follower, window, cx| {
17411            follower.apply_update_proto(
17412                &project,
17413                update_message.borrow().clone().unwrap(),
17414                window,
17415                cx,
17416            )
17417        })
17418        .await
17419        .unwrap();
17420    update_message.borrow_mut().take();
17421    assert_eq!(
17422        follower_1.update(cx, |editor, cx| editor.text(cx)),
17423        leader.update(cx, |editor, cx| editor.text(cx))
17424    );
17425}
17426
17427#[gpui::test]
17428async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17429    init_test(cx, |_| {});
17430
17431    let mut cx = EditorTestContext::new(cx).await;
17432    let lsp_store =
17433        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17434
17435    cx.set_state(indoc! {"
17436        ˇfn func(abc def: i32) -> u32 {
17437        }
17438    "});
17439
17440    cx.update(|_, cx| {
17441        lsp_store.update(cx, |lsp_store, cx| {
17442            lsp_store
17443                .update_diagnostics(
17444                    LanguageServerId(0),
17445                    lsp::PublishDiagnosticsParams {
17446                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17447                        version: None,
17448                        diagnostics: vec![
17449                            lsp::Diagnostic {
17450                                range: lsp::Range::new(
17451                                    lsp::Position::new(0, 11),
17452                                    lsp::Position::new(0, 12),
17453                                ),
17454                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17455                                ..Default::default()
17456                            },
17457                            lsp::Diagnostic {
17458                                range: lsp::Range::new(
17459                                    lsp::Position::new(0, 12),
17460                                    lsp::Position::new(0, 15),
17461                                ),
17462                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17463                                ..Default::default()
17464                            },
17465                            lsp::Diagnostic {
17466                                range: lsp::Range::new(
17467                                    lsp::Position::new(0, 25),
17468                                    lsp::Position::new(0, 28),
17469                                ),
17470                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17471                                ..Default::default()
17472                            },
17473                        ],
17474                    },
17475                    None,
17476                    DiagnosticSourceKind::Pushed,
17477                    &[],
17478                    cx,
17479                )
17480                .unwrap()
17481        });
17482    });
17483
17484    executor.run_until_parked();
17485
17486    cx.update_editor(|editor, window, cx| {
17487        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17488    });
17489
17490    cx.assert_editor_state(indoc! {"
17491        fn func(abc def: i32) -> ˇu32 {
17492        }
17493    "});
17494
17495    cx.update_editor(|editor, window, cx| {
17496        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17497    });
17498
17499    cx.assert_editor_state(indoc! {"
17500        fn func(abc ˇdef: i32) -> u32 {
17501        }
17502    "});
17503
17504    cx.update_editor(|editor, window, cx| {
17505        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17506    });
17507
17508    cx.assert_editor_state(indoc! {"
17509        fn func(abcˇ def: i32) -> u32 {
17510        }
17511    "});
17512
17513    cx.update_editor(|editor, window, cx| {
17514        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17515    });
17516
17517    cx.assert_editor_state(indoc! {"
17518        fn func(abc def: i32) -> ˇu32 {
17519        }
17520    "});
17521}
17522
17523#[gpui::test]
17524async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17525    init_test(cx, |_| {});
17526
17527    let mut cx = EditorTestContext::new(cx).await;
17528
17529    let diff_base = r#"
17530        use some::mod;
17531
17532        const A: u32 = 42;
17533
17534        fn main() {
17535            println!("hello");
17536
17537            println!("world");
17538        }
17539        "#
17540    .unindent();
17541
17542    // Edits are modified, removed, modified, added
17543    cx.set_state(
17544        &r#"
17545        use some::modified;
17546
17547        ˇ
17548        fn main() {
17549            println!("hello there");
17550
17551            println!("around the");
17552            println!("world");
17553        }
17554        "#
17555        .unindent(),
17556    );
17557
17558    cx.set_head_text(&diff_base);
17559    executor.run_until_parked();
17560
17561    cx.update_editor(|editor, window, cx| {
17562        //Wrap around the bottom of the buffer
17563        for _ in 0..3 {
17564            editor.go_to_next_hunk(&GoToHunk, window, cx);
17565        }
17566    });
17567
17568    cx.assert_editor_state(
17569        &r#"
17570        ˇuse some::modified;
17571
17572
17573        fn main() {
17574            println!("hello there");
17575
17576            println!("around the");
17577            println!("world");
17578        }
17579        "#
17580        .unindent(),
17581    );
17582
17583    cx.update_editor(|editor, window, cx| {
17584        //Wrap around the top of the buffer
17585        for _ in 0..2 {
17586            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17587        }
17588    });
17589
17590    cx.assert_editor_state(
17591        &r#"
17592        use some::modified;
17593
17594
17595        fn main() {
17596        ˇ    println!("hello there");
17597
17598            println!("around the");
17599            println!("world");
17600        }
17601        "#
17602        .unindent(),
17603    );
17604
17605    cx.update_editor(|editor, window, cx| {
17606        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17607    });
17608
17609    cx.assert_editor_state(
17610        &r#"
17611        use some::modified;
17612
17613        ˇ
17614        fn main() {
17615            println!("hello there");
17616
17617            println!("around the");
17618            println!("world");
17619        }
17620        "#
17621        .unindent(),
17622    );
17623
17624    cx.update_editor(|editor, window, cx| {
17625        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17626    });
17627
17628    cx.assert_editor_state(
17629        &r#"
17630        ˇuse some::modified;
17631
17632
17633        fn main() {
17634            println!("hello there");
17635
17636            println!("around the");
17637            println!("world");
17638        }
17639        "#
17640        .unindent(),
17641    );
17642
17643    cx.update_editor(|editor, window, cx| {
17644        for _ in 0..2 {
17645            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17646        }
17647    });
17648
17649    cx.assert_editor_state(
17650        &r#"
17651        use some::modified;
17652
17653
17654        fn main() {
17655        ˇ    println!("hello there");
17656
17657            println!("around the");
17658            println!("world");
17659        }
17660        "#
17661        .unindent(),
17662    );
17663
17664    cx.update_editor(|editor, window, cx| {
17665        editor.fold(&Fold, window, cx);
17666    });
17667
17668    cx.update_editor(|editor, window, cx| {
17669        editor.go_to_next_hunk(&GoToHunk, window, cx);
17670    });
17671
17672    cx.assert_editor_state(
17673        &r#"
17674        ˇuse some::modified;
17675
17676
17677        fn main() {
17678            println!("hello there");
17679
17680            println!("around the");
17681            println!("world");
17682        }
17683        "#
17684        .unindent(),
17685    );
17686}
17687
17688#[test]
17689fn test_split_words() {
17690    fn split(text: &str) -> Vec<&str> {
17691        split_words(text).collect()
17692    }
17693
17694    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17695    assert_eq!(split("hello_world"), &["hello_", "world"]);
17696    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17697    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17698    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17699    assert_eq!(split("helloworld"), &["helloworld"]);
17700
17701    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17702}
17703
17704#[test]
17705fn test_split_words_for_snippet_prefix() {
17706    fn split(text: &str) -> Vec<&str> {
17707        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17708    }
17709
17710    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17711    assert_eq!(split("hello_world"), &["hello_world"]);
17712    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17713    assert_eq!(split("Hello_World"), &["Hello_World"]);
17714    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17715    assert_eq!(split("helloworld"), &["helloworld"]);
17716    assert_eq!(
17717        split("this@is!@#$^many   . symbols"),
17718        &[
17719            "symbols",
17720            " symbols",
17721            ". symbols",
17722            " . symbols",
17723            "  . symbols",
17724            "   . symbols",
17725            "many   . symbols",
17726            "^many   . symbols",
17727            "$^many   . symbols",
17728            "#$^many   . symbols",
17729            "@#$^many   . symbols",
17730            "!@#$^many   . symbols",
17731            "is!@#$^many   . symbols",
17732            "@is!@#$^many   . symbols",
17733            "this@is!@#$^many   . symbols",
17734        ],
17735    );
17736    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17737}
17738
17739#[gpui::test]
17740async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17741    init_test(cx, |_| {});
17742
17743    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17744
17745    #[track_caller]
17746    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17747        let _state_context = cx.set_state(before);
17748        cx.run_until_parked();
17749        cx.update_editor(|editor, window, cx| {
17750            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17751        });
17752        cx.run_until_parked();
17753        cx.assert_editor_state(after);
17754    }
17755
17756    // Outside bracket jumps to outside of matching bracket
17757    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17758    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17759
17760    // Inside bracket jumps to inside of matching bracket
17761    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17762    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17763
17764    // When outside a bracket and inside, favor jumping to the inside bracket
17765    assert(
17766        "console.log('foo', [1, 2, 3]ˇ);",
17767        "console.log('foo', ˇ[1, 2, 3]);",
17768        &mut cx,
17769    );
17770    assert(
17771        "console.log(ˇ'foo', [1, 2, 3]);",
17772        "console.log('foo'ˇ, [1, 2, 3]);",
17773        &mut cx,
17774    );
17775
17776    // Bias forward if two options are equally likely
17777    assert(
17778        "let result = curried_fun()ˇ();",
17779        "let result = curried_fun()()ˇ;",
17780        &mut cx,
17781    );
17782
17783    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17784    assert(
17785        indoc! {"
17786            function test() {
17787                console.log('test')ˇ
17788            }"},
17789        indoc! {"
17790            function test() {
17791                console.logˇ('test')
17792            }"},
17793        &mut cx,
17794    );
17795}
17796
17797#[gpui::test]
17798async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
17799    init_test(cx, |_| {});
17800    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
17801    language_registry.add(markdown_lang());
17802    language_registry.add(rust_lang());
17803    let buffer = cx.new(|cx| {
17804        let mut buffer = language::Buffer::local(
17805            indoc! {"
17806            ```rs
17807            impl Worktree {
17808                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17809                }
17810            }
17811            ```
17812        "},
17813            cx,
17814        );
17815        buffer.set_language_registry(language_registry.clone());
17816        buffer.set_language(Some(markdown_lang()), cx);
17817        buffer
17818    });
17819    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17820    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17821    cx.executor().run_until_parked();
17822    _ = editor.update(cx, |editor, window, cx| {
17823        // Case 1: Test outer enclosing brackets
17824        select_ranges(
17825            editor,
17826            &indoc! {"
17827                ```rs
17828                impl Worktree {
17829                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17830                    }
1783117832                ```
17833            "},
17834            window,
17835            cx,
17836        );
17837        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17838        assert_text_with_selections(
17839            editor,
17840            &indoc! {"
17841                ```rs
17842                impl Worktree ˇ{
17843                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17844                    }
17845                }
17846                ```
17847            "},
17848            cx,
17849        );
17850        // Case 2: Test inner enclosing brackets
17851        select_ranges(
17852            editor,
17853            &indoc! {"
17854                ```rs
17855                impl Worktree {
17856                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1785717858                }
17859                ```
17860            "},
17861            window,
17862            cx,
17863        );
17864        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17865        assert_text_with_selections(
17866            editor,
17867            &indoc! {"
17868                ```rs
17869                impl Worktree {
17870                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
17871                    }
17872                }
17873                ```
17874            "},
17875            cx,
17876        );
17877    });
17878}
17879
17880#[gpui::test]
17881async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17882    init_test(cx, |_| {});
17883
17884    let fs = FakeFs::new(cx.executor());
17885    fs.insert_tree(
17886        path!("/a"),
17887        json!({
17888            "main.rs": "fn main() { let a = 5; }",
17889            "other.rs": "// Test file",
17890        }),
17891    )
17892    .await;
17893    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17894
17895    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17896    language_registry.add(Arc::new(Language::new(
17897        LanguageConfig {
17898            name: "Rust".into(),
17899            matcher: LanguageMatcher {
17900                path_suffixes: vec!["rs".to_string()],
17901                ..Default::default()
17902            },
17903            brackets: BracketPairConfig {
17904                pairs: vec![BracketPair {
17905                    start: "{".to_string(),
17906                    end: "}".to_string(),
17907                    close: true,
17908                    surround: true,
17909                    newline: true,
17910                }],
17911                disabled_scopes_by_bracket_ix: Vec::new(),
17912            },
17913            ..Default::default()
17914        },
17915        Some(tree_sitter_rust::LANGUAGE.into()),
17916    )));
17917    let mut fake_servers = language_registry.register_fake_lsp(
17918        "Rust",
17919        FakeLspAdapter {
17920            capabilities: lsp::ServerCapabilities {
17921                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17922                    first_trigger_character: "{".to_string(),
17923                    more_trigger_character: None,
17924                }),
17925                ..Default::default()
17926            },
17927            ..Default::default()
17928        },
17929    );
17930
17931    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17932
17933    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17934
17935    let worktree_id = workspace
17936        .update(cx, |workspace, _, cx| {
17937            workspace.project().update(cx, |project, cx| {
17938                project.worktrees(cx).next().unwrap().read(cx).id()
17939            })
17940        })
17941        .unwrap();
17942
17943    let buffer = project
17944        .update(cx, |project, cx| {
17945            project.open_local_buffer(path!("/a/main.rs"), cx)
17946        })
17947        .await
17948        .unwrap();
17949    let editor_handle = workspace
17950        .update(cx, |workspace, window, cx| {
17951            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17952        })
17953        .unwrap()
17954        .await
17955        .unwrap()
17956        .downcast::<Editor>()
17957        .unwrap();
17958
17959    cx.executor().start_waiting();
17960    let fake_server = fake_servers.next().await.unwrap();
17961
17962    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17963        |params, _| async move {
17964            assert_eq!(
17965                params.text_document_position.text_document.uri,
17966                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17967            );
17968            assert_eq!(
17969                params.text_document_position.position,
17970                lsp::Position::new(0, 21),
17971            );
17972
17973            Ok(Some(vec![lsp::TextEdit {
17974                new_text: "]".to_string(),
17975                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17976            }]))
17977        },
17978    );
17979
17980    editor_handle.update_in(cx, |editor, window, cx| {
17981        window.focus(&editor.focus_handle(cx));
17982        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17983            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17984        });
17985        editor.handle_input("{", window, cx);
17986    });
17987
17988    cx.executor().run_until_parked();
17989
17990    buffer.update(cx, |buffer, _| {
17991        assert_eq!(
17992            buffer.text(),
17993            "fn main() { let a = {5}; }",
17994            "No extra braces from on type formatting should appear in the buffer"
17995        )
17996    });
17997}
17998
17999#[gpui::test(iterations = 20, seeds(31))]
18000async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
18001    init_test(cx, |_| {});
18002
18003    let mut cx = EditorLspTestContext::new_rust(
18004        lsp::ServerCapabilities {
18005            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
18006                first_trigger_character: ".".to_string(),
18007                more_trigger_character: None,
18008            }),
18009            ..Default::default()
18010        },
18011        cx,
18012    )
18013    .await;
18014
18015    cx.update_buffer(|buffer, _| {
18016        // This causes autoindent to be async.
18017        buffer.set_sync_parse_timeout(Duration::ZERO)
18018    });
18019
18020    cx.set_state("fn c() {\n    d()ˇ\n}\n");
18021    cx.simulate_keystroke("\n");
18022    cx.run_until_parked();
18023
18024    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
18025    let mut request =
18026        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
18027            let buffer_cloned = buffer_cloned.clone();
18028            async move {
18029                buffer_cloned.update(&mut cx, |buffer, _| {
18030                    assert_eq!(
18031                        buffer.text(),
18032                        "fn c() {\n    d()\n        .\n}\n",
18033                        "OnTypeFormatting should triggered after autoindent applied"
18034                    )
18035                })?;
18036
18037                Ok(Some(vec![]))
18038            }
18039        });
18040
18041    cx.simulate_keystroke(".");
18042    cx.run_until_parked();
18043
18044    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
18045    assert!(request.next().await.is_some());
18046    request.close();
18047    assert!(request.next().await.is_none());
18048}
18049
18050#[gpui::test]
18051async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
18052    init_test(cx, |_| {});
18053
18054    let fs = FakeFs::new(cx.executor());
18055    fs.insert_tree(
18056        path!("/a"),
18057        json!({
18058            "main.rs": "fn main() { let a = 5; }",
18059            "other.rs": "// Test file",
18060        }),
18061    )
18062    .await;
18063
18064    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
18065
18066    let server_restarts = Arc::new(AtomicUsize::new(0));
18067    let closure_restarts = Arc::clone(&server_restarts);
18068    let language_server_name = "test language server";
18069    let language_name: LanguageName = "Rust".into();
18070
18071    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18072    language_registry.add(Arc::new(Language::new(
18073        LanguageConfig {
18074            name: language_name.clone(),
18075            matcher: LanguageMatcher {
18076                path_suffixes: vec!["rs".to_string()],
18077                ..Default::default()
18078            },
18079            ..Default::default()
18080        },
18081        Some(tree_sitter_rust::LANGUAGE.into()),
18082    )));
18083    let mut fake_servers = language_registry.register_fake_lsp(
18084        "Rust",
18085        FakeLspAdapter {
18086            name: language_server_name,
18087            initialization_options: Some(json!({
18088                "testOptionValue": true
18089            })),
18090            initializer: Some(Box::new(move |fake_server| {
18091                let task_restarts = Arc::clone(&closure_restarts);
18092                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
18093                    task_restarts.fetch_add(1, atomic::Ordering::Release);
18094                    futures::future::ready(Ok(()))
18095                });
18096            })),
18097            ..Default::default()
18098        },
18099    );
18100
18101    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
18102    let _buffer = project
18103        .update(cx, |project, cx| {
18104            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
18105        })
18106        .await
18107        .unwrap();
18108    let _fake_server = fake_servers.next().await.unwrap();
18109    update_test_language_settings(cx, |language_settings| {
18110        language_settings.languages.0.insert(
18111            language_name.clone().0,
18112            LanguageSettingsContent {
18113                tab_size: NonZeroU32::new(8),
18114                ..Default::default()
18115            },
18116        );
18117    });
18118    cx.executor().run_until_parked();
18119    assert_eq!(
18120        server_restarts.load(atomic::Ordering::Acquire),
18121        0,
18122        "Should not restart LSP server on an unrelated change"
18123    );
18124
18125    update_test_project_settings(cx, |project_settings| {
18126        project_settings.lsp.insert(
18127            "Some other server name".into(),
18128            LspSettings {
18129                binary: None,
18130                settings: None,
18131                initialization_options: Some(json!({
18132                    "some other init value": false
18133                })),
18134                enable_lsp_tasks: false,
18135                fetch: None,
18136            },
18137        );
18138    });
18139    cx.executor().run_until_parked();
18140    assert_eq!(
18141        server_restarts.load(atomic::Ordering::Acquire),
18142        0,
18143        "Should not restart LSP server on an unrelated LSP settings change"
18144    );
18145
18146    update_test_project_settings(cx, |project_settings| {
18147        project_settings.lsp.insert(
18148            language_server_name.into(),
18149            LspSettings {
18150                binary: None,
18151                settings: None,
18152                initialization_options: Some(json!({
18153                    "anotherInitValue": false
18154                })),
18155                enable_lsp_tasks: false,
18156                fetch: None,
18157            },
18158        );
18159    });
18160    cx.executor().run_until_parked();
18161    assert_eq!(
18162        server_restarts.load(atomic::Ordering::Acquire),
18163        1,
18164        "Should restart LSP server on a related LSP settings change"
18165    );
18166
18167    update_test_project_settings(cx, |project_settings| {
18168        project_settings.lsp.insert(
18169            language_server_name.into(),
18170            LspSettings {
18171                binary: None,
18172                settings: None,
18173                initialization_options: Some(json!({
18174                    "anotherInitValue": false
18175                })),
18176                enable_lsp_tasks: false,
18177                fetch: None,
18178            },
18179        );
18180    });
18181    cx.executor().run_until_parked();
18182    assert_eq!(
18183        server_restarts.load(atomic::Ordering::Acquire),
18184        1,
18185        "Should not restart LSP server on a related LSP settings change that is the same"
18186    );
18187
18188    update_test_project_settings(cx, |project_settings| {
18189        project_settings.lsp.insert(
18190            language_server_name.into(),
18191            LspSettings {
18192                binary: None,
18193                settings: None,
18194                initialization_options: None,
18195                enable_lsp_tasks: false,
18196                fetch: None,
18197            },
18198        );
18199    });
18200    cx.executor().run_until_parked();
18201    assert_eq!(
18202        server_restarts.load(atomic::Ordering::Acquire),
18203        2,
18204        "Should restart LSP server on another related LSP settings change"
18205    );
18206}
18207
18208#[gpui::test]
18209async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18210    init_test(cx, |_| {});
18211
18212    let mut cx = EditorLspTestContext::new_rust(
18213        lsp::ServerCapabilities {
18214            completion_provider: Some(lsp::CompletionOptions {
18215                trigger_characters: Some(vec![".".to_string()]),
18216                resolve_provider: Some(true),
18217                ..Default::default()
18218            }),
18219            ..Default::default()
18220        },
18221        cx,
18222    )
18223    .await;
18224
18225    cx.set_state("fn main() { let a = 2ˇ; }");
18226    cx.simulate_keystroke(".");
18227    let completion_item = lsp::CompletionItem {
18228        label: "some".into(),
18229        kind: Some(lsp::CompletionItemKind::SNIPPET),
18230        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18231        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18232            kind: lsp::MarkupKind::Markdown,
18233            value: "```rust\nSome(2)\n```".to_string(),
18234        })),
18235        deprecated: Some(false),
18236        sort_text: Some("fffffff2".to_string()),
18237        filter_text: Some("some".to_string()),
18238        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18239        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18240            range: lsp::Range {
18241                start: lsp::Position {
18242                    line: 0,
18243                    character: 22,
18244                },
18245                end: lsp::Position {
18246                    line: 0,
18247                    character: 22,
18248                },
18249            },
18250            new_text: "Some(2)".to_string(),
18251        })),
18252        additional_text_edits: Some(vec![lsp::TextEdit {
18253            range: lsp::Range {
18254                start: lsp::Position {
18255                    line: 0,
18256                    character: 20,
18257                },
18258                end: lsp::Position {
18259                    line: 0,
18260                    character: 22,
18261                },
18262            },
18263            new_text: "".to_string(),
18264        }]),
18265        ..Default::default()
18266    };
18267
18268    let closure_completion_item = completion_item.clone();
18269    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18270        let task_completion_item = closure_completion_item.clone();
18271        async move {
18272            Ok(Some(lsp::CompletionResponse::Array(vec![
18273                task_completion_item,
18274            ])))
18275        }
18276    });
18277
18278    request.next().await;
18279
18280    cx.condition(|editor, _| editor.context_menu_visible())
18281        .await;
18282    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18283        editor
18284            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18285            .unwrap()
18286    });
18287    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18288
18289    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18290        let task_completion_item = completion_item.clone();
18291        async move { Ok(task_completion_item) }
18292    })
18293    .next()
18294    .await
18295    .unwrap();
18296    apply_additional_edits.await.unwrap();
18297    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18298}
18299
18300#[gpui::test]
18301async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18302    init_test(cx, |_| {});
18303
18304    let mut cx = EditorLspTestContext::new_rust(
18305        lsp::ServerCapabilities {
18306            completion_provider: Some(lsp::CompletionOptions {
18307                trigger_characters: Some(vec![".".to_string()]),
18308                resolve_provider: Some(true),
18309                ..Default::default()
18310            }),
18311            ..Default::default()
18312        },
18313        cx,
18314    )
18315    .await;
18316
18317    cx.set_state("fn main() { let a = 2ˇ; }");
18318    cx.simulate_keystroke(".");
18319
18320    let item1 = lsp::CompletionItem {
18321        label: "method id()".to_string(),
18322        filter_text: Some("id".to_string()),
18323        detail: None,
18324        documentation: None,
18325        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18326            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18327            new_text: ".id".to_string(),
18328        })),
18329        ..lsp::CompletionItem::default()
18330    };
18331
18332    let item2 = lsp::CompletionItem {
18333        label: "other".to_string(),
18334        filter_text: Some("other".to_string()),
18335        detail: None,
18336        documentation: None,
18337        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18338            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18339            new_text: ".other".to_string(),
18340        })),
18341        ..lsp::CompletionItem::default()
18342    };
18343
18344    let item1 = item1.clone();
18345    cx.set_request_handler::<lsp::request::Completion, _, _>({
18346        let item1 = item1.clone();
18347        move |_, _, _| {
18348            let item1 = item1.clone();
18349            let item2 = item2.clone();
18350            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18351        }
18352    })
18353    .next()
18354    .await;
18355
18356    cx.condition(|editor, _| editor.context_menu_visible())
18357        .await;
18358    cx.update_editor(|editor, _, _| {
18359        let context_menu = editor.context_menu.borrow_mut();
18360        let context_menu = context_menu
18361            .as_ref()
18362            .expect("Should have the context menu deployed");
18363        match context_menu {
18364            CodeContextMenu::Completions(completions_menu) => {
18365                let completions = completions_menu.completions.borrow_mut();
18366                assert_eq!(
18367                    completions
18368                        .iter()
18369                        .map(|completion| &completion.label.text)
18370                        .collect::<Vec<_>>(),
18371                    vec!["method id()", "other"]
18372                )
18373            }
18374            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18375        }
18376    });
18377
18378    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18379        let item1 = item1.clone();
18380        move |_, item_to_resolve, _| {
18381            let item1 = item1.clone();
18382            async move {
18383                if item1 == item_to_resolve {
18384                    Ok(lsp::CompletionItem {
18385                        label: "method id()".to_string(),
18386                        filter_text: Some("id".to_string()),
18387                        detail: Some("Now resolved!".to_string()),
18388                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18389                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18390                            range: lsp::Range::new(
18391                                lsp::Position::new(0, 22),
18392                                lsp::Position::new(0, 22),
18393                            ),
18394                            new_text: ".id".to_string(),
18395                        })),
18396                        ..lsp::CompletionItem::default()
18397                    })
18398                } else {
18399                    Ok(item_to_resolve)
18400                }
18401            }
18402        }
18403    })
18404    .next()
18405    .await
18406    .unwrap();
18407    cx.run_until_parked();
18408
18409    cx.update_editor(|editor, window, cx| {
18410        editor.context_menu_next(&Default::default(), window, cx);
18411    });
18412
18413    cx.update_editor(|editor, _, _| {
18414        let context_menu = editor.context_menu.borrow_mut();
18415        let context_menu = context_menu
18416            .as_ref()
18417            .expect("Should have the context menu deployed");
18418        match context_menu {
18419            CodeContextMenu::Completions(completions_menu) => {
18420                let completions = completions_menu.completions.borrow_mut();
18421                assert_eq!(
18422                    completions
18423                        .iter()
18424                        .map(|completion| &completion.label.text)
18425                        .collect::<Vec<_>>(),
18426                    vec!["method id() Now resolved!", "other"],
18427                    "Should update first completion label, but not second as the filter text did not match."
18428                );
18429            }
18430            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18431        }
18432    });
18433}
18434
18435#[gpui::test]
18436async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18437    init_test(cx, |_| {});
18438    let mut cx = EditorLspTestContext::new_rust(
18439        lsp::ServerCapabilities {
18440            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18441            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18442            completion_provider: Some(lsp::CompletionOptions {
18443                resolve_provider: Some(true),
18444                ..Default::default()
18445            }),
18446            ..Default::default()
18447        },
18448        cx,
18449    )
18450    .await;
18451    cx.set_state(indoc! {"
18452        struct TestStruct {
18453            field: i32
18454        }
18455
18456        fn mainˇ() {
18457            let unused_var = 42;
18458            let test_struct = TestStruct { field: 42 };
18459        }
18460    "});
18461    let symbol_range = cx.lsp_range(indoc! {"
18462        struct TestStruct {
18463            field: i32
18464        }
18465
18466        «fn main»() {
18467            let unused_var = 42;
18468            let test_struct = TestStruct { field: 42 };
18469        }
18470    "});
18471    let mut hover_requests =
18472        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18473            Ok(Some(lsp::Hover {
18474                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18475                    kind: lsp::MarkupKind::Markdown,
18476                    value: "Function documentation".to_string(),
18477                }),
18478                range: Some(symbol_range),
18479            }))
18480        });
18481
18482    // Case 1: Test that code action menu hide hover popover
18483    cx.dispatch_action(Hover);
18484    hover_requests.next().await;
18485    cx.condition(|editor, _| editor.hover_state.visible()).await;
18486    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18487        move |_, _, _| async move {
18488            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18489                lsp::CodeAction {
18490                    title: "Remove unused variable".to_string(),
18491                    kind: Some(CodeActionKind::QUICKFIX),
18492                    edit: Some(lsp::WorkspaceEdit {
18493                        changes: Some(
18494                            [(
18495                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18496                                vec![lsp::TextEdit {
18497                                    range: lsp::Range::new(
18498                                        lsp::Position::new(5, 4),
18499                                        lsp::Position::new(5, 27),
18500                                    ),
18501                                    new_text: "".to_string(),
18502                                }],
18503                            )]
18504                            .into_iter()
18505                            .collect(),
18506                        ),
18507                        ..Default::default()
18508                    }),
18509                    ..Default::default()
18510                },
18511            )]))
18512        },
18513    );
18514    cx.update_editor(|editor, window, cx| {
18515        editor.toggle_code_actions(
18516            &ToggleCodeActions {
18517                deployed_from: None,
18518                quick_launch: false,
18519            },
18520            window,
18521            cx,
18522        );
18523    });
18524    code_action_requests.next().await;
18525    cx.run_until_parked();
18526    cx.condition(|editor, _| editor.context_menu_visible())
18527        .await;
18528    cx.update_editor(|editor, _, _| {
18529        assert!(
18530            !editor.hover_state.visible(),
18531            "Hover popover should be hidden when code action menu is shown"
18532        );
18533        // Hide code actions
18534        editor.context_menu.take();
18535    });
18536
18537    // Case 2: Test that code completions hide hover popover
18538    cx.dispatch_action(Hover);
18539    hover_requests.next().await;
18540    cx.condition(|editor, _| editor.hover_state.visible()).await;
18541    let counter = Arc::new(AtomicUsize::new(0));
18542    let mut completion_requests =
18543        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18544            let counter = counter.clone();
18545            async move {
18546                counter.fetch_add(1, atomic::Ordering::Release);
18547                Ok(Some(lsp::CompletionResponse::Array(vec![
18548                    lsp::CompletionItem {
18549                        label: "main".into(),
18550                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18551                        detail: Some("() -> ()".to_string()),
18552                        ..Default::default()
18553                    },
18554                    lsp::CompletionItem {
18555                        label: "TestStruct".into(),
18556                        kind: Some(lsp::CompletionItemKind::STRUCT),
18557                        detail: Some("struct TestStruct".to_string()),
18558                        ..Default::default()
18559                    },
18560                ])))
18561            }
18562        });
18563    cx.update_editor(|editor, window, cx| {
18564        editor.show_completions(&ShowCompletions, window, cx);
18565    });
18566    completion_requests.next().await;
18567    cx.condition(|editor, _| editor.context_menu_visible())
18568        .await;
18569    cx.update_editor(|editor, _, _| {
18570        assert!(
18571            !editor.hover_state.visible(),
18572            "Hover popover should be hidden when completion menu is shown"
18573        );
18574    });
18575}
18576
18577#[gpui::test]
18578async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18579    init_test(cx, |_| {});
18580
18581    let mut cx = EditorLspTestContext::new_rust(
18582        lsp::ServerCapabilities {
18583            completion_provider: Some(lsp::CompletionOptions {
18584                trigger_characters: Some(vec![".".to_string()]),
18585                resolve_provider: Some(true),
18586                ..Default::default()
18587            }),
18588            ..Default::default()
18589        },
18590        cx,
18591    )
18592    .await;
18593
18594    cx.set_state("fn main() { let a = 2ˇ; }");
18595    cx.simulate_keystroke(".");
18596
18597    let unresolved_item_1 = lsp::CompletionItem {
18598        label: "id".to_string(),
18599        filter_text: Some("id".to_string()),
18600        detail: None,
18601        documentation: None,
18602        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18603            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18604            new_text: ".id".to_string(),
18605        })),
18606        ..lsp::CompletionItem::default()
18607    };
18608    let resolved_item_1 = lsp::CompletionItem {
18609        additional_text_edits: Some(vec![lsp::TextEdit {
18610            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18611            new_text: "!!".to_string(),
18612        }]),
18613        ..unresolved_item_1.clone()
18614    };
18615    let unresolved_item_2 = lsp::CompletionItem {
18616        label: "other".to_string(),
18617        filter_text: Some("other".to_string()),
18618        detail: None,
18619        documentation: None,
18620        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18621            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18622            new_text: ".other".to_string(),
18623        })),
18624        ..lsp::CompletionItem::default()
18625    };
18626    let resolved_item_2 = lsp::CompletionItem {
18627        additional_text_edits: Some(vec![lsp::TextEdit {
18628            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18629            new_text: "??".to_string(),
18630        }]),
18631        ..unresolved_item_2.clone()
18632    };
18633
18634    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18635    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18636    cx.lsp
18637        .server
18638        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18639            let unresolved_item_1 = unresolved_item_1.clone();
18640            let resolved_item_1 = resolved_item_1.clone();
18641            let unresolved_item_2 = unresolved_item_2.clone();
18642            let resolved_item_2 = resolved_item_2.clone();
18643            let resolve_requests_1 = resolve_requests_1.clone();
18644            let resolve_requests_2 = resolve_requests_2.clone();
18645            move |unresolved_request, _| {
18646                let unresolved_item_1 = unresolved_item_1.clone();
18647                let resolved_item_1 = resolved_item_1.clone();
18648                let unresolved_item_2 = unresolved_item_2.clone();
18649                let resolved_item_2 = resolved_item_2.clone();
18650                let resolve_requests_1 = resolve_requests_1.clone();
18651                let resolve_requests_2 = resolve_requests_2.clone();
18652                async move {
18653                    if unresolved_request == unresolved_item_1 {
18654                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18655                        Ok(resolved_item_1.clone())
18656                    } else if unresolved_request == unresolved_item_2 {
18657                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18658                        Ok(resolved_item_2.clone())
18659                    } else {
18660                        panic!("Unexpected completion item {unresolved_request:?}")
18661                    }
18662                }
18663            }
18664        })
18665        .detach();
18666
18667    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18668        let unresolved_item_1 = unresolved_item_1.clone();
18669        let unresolved_item_2 = unresolved_item_2.clone();
18670        async move {
18671            Ok(Some(lsp::CompletionResponse::Array(vec![
18672                unresolved_item_1,
18673                unresolved_item_2,
18674            ])))
18675        }
18676    })
18677    .next()
18678    .await;
18679
18680    cx.condition(|editor, _| editor.context_menu_visible())
18681        .await;
18682    cx.update_editor(|editor, _, _| {
18683        let context_menu = editor.context_menu.borrow_mut();
18684        let context_menu = context_menu
18685            .as_ref()
18686            .expect("Should have the context menu deployed");
18687        match context_menu {
18688            CodeContextMenu::Completions(completions_menu) => {
18689                let completions = completions_menu.completions.borrow_mut();
18690                assert_eq!(
18691                    completions
18692                        .iter()
18693                        .map(|completion| &completion.label.text)
18694                        .collect::<Vec<_>>(),
18695                    vec!["id", "other"]
18696                )
18697            }
18698            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18699        }
18700    });
18701    cx.run_until_parked();
18702
18703    cx.update_editor(|editor, window, cx| {
18704        editor.context_menu_next(&ContextMenuNext, window, cx);
18705    });
18706    cx.run_until_parked();
18707    cx.update_editor(|editor, window, cx| {
18708        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18709    });
18710    cx.run_until_parked();
18711    cx.update_editor(|editor, window, cx| {
18712        editor.context_menu_next(&ContextMenuNext, window, cx);
18713    });
18714    cx.run_until_parked();
18715    cx.update_editor(|editor, window, cx| {
18716        editor
18717            .compose_completion(&ComposeCompletion::default(), window, cx)
18718            .expect("No task returned")
18719    })
18720    .await
18721    .expect("Completion failed");
18722    cx.run_until_parked();
18723
18724    cx.update_editor(|editor, _, cx| {
18725        assert_eq!(
18726            resolve_requests_1.load(atomic::Ordering::Acquire),
18727            1,
18728            "Should always resolve once despite multiple selections"
18729        );
18730        assert_eq!(
18731            resolve_requests_2.load(atomic::Ordering::Acquire),
18732            1,
18733            "Should always resolve once after multiple selections and applying the completion"
18734        );
18735        assert_eq!(
18736            editor.text(cx),
18737            "fn main() { let a = ??.other; }",
18738            "Should use resolved data when applying the completion"
18739        );
18740    });
18741}
18742
18743#[gpui::test]
18744async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18745    init_test(cx, |_| {});
18746
18747    let item_0 = lsp::CompletionItem {
18748        label: "abs".into(),
18749        insert_text: Some("abs".into()),
18750        data: Some(json!({ "very": "special"})),
18751        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18752        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18753            lsp::InsertReplaceEdit {
18754                new_text: "abs".to_string(),
18755                insert: lsp::Range::default(),
18756                replace: lsp::Range::default(),
18757            },
18758        )),
18759        ..lsp::CompletionItem::default()
18760    };
18761    let items = iter::once(item_0.clone())
18762        .chain((11..51).map(|i| lsp::CompletionItem {
18763            label: format!("item_{}", i),
18764            insert_text: Some(format!("item_{}", i)),
18765            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18766            ..lsp::CompletionItem::default()
18767        }))
18768        .collect::<Vec<_>>();
18769
18770    let default_commit_characters = vec!["?".to_string()];
18771    let default_data = json!({ "default": "data"});
18772    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18773    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18774    let default_edit_range = lsp::Range {
18775        start: lsp::Position {
18776            line: 0,
18777            character: 5,
18778        },
18779        end: lsp::Position {
18780            line: 0,
18781            character: 5,
18782        },
18783    };
18784
18785    let mut cx = EditorLspTestContext::new_rust(
18786        lsp::ServerCapabilities {
18787            completion_provider: Some(lsp::CompletionOptions {
18788                trigger_characters: Some(vec![".".to_string()]),
18789                resolve_provider: Some(true),
18790                ..Default::default()
18791            }),
18792            ..Default::default()
18793        },
18794        cx,
18795    )
18796    .await;
18797
18798    cx.set_state("fn main() { let a = 2ˇ; }");
18799    cx.simulate_keystroke(".");
18800
18801    let completion_data = default_data.clone();
18802    let completion_characters = default_commit_characters.clone();
18803    let completion_items = items.clone();
18804    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18805        let default_data = completion_data.clone();
18806        let default_commit_characters = completion_characters.clone();
18807        let items = completion_items.clone();
18808        async move {
18809            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18810                items,
18811                item_defaults: Some(lsp::CompletionListItemDefaults {
18812                    data: Some(default_data.clone()),
18813                    commit_characters: Some(default_commit_characters.clone()),
18814                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18815                        default_edit_range,
18816                    )),
18817                    insert_text_format: Some(default_insert_text_format),
18818                    insert_text_mode: Some(default_insert_text_mode),
18819                }),
18820                ..lsp::CompletionList::default()
18821            })))
18822        }
18823    })
18824    .next()
18825    .await;
18826
18827    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18828    cx.lsp
18829        .server
18830        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18831            let closure_resolved_items = resolved_items.clone();
18832            move |item_to_resolve, _| {
18833                let closure_resolved_items = closure_resolved_items.clone();
18834                async move {
18835                    closure_resolved_items.lock().push(item_to_resolve.clone());
18836                    Ok(item_to_resolve)
18837                }
18838            }
18839        })
18840        .detach();
18841
18842    cx.condition(|editor, _| editor.context_menu_visible())
18843        .await;
18844    cx.run_until_parked();
18845    cx.update_editor(|editor, _, _| {
18846        let menu = editor.context_menu.borrow_mut();
18847        match menu.as_ref().expect("should have the completions menu") {
18848            CodeContextMenu::Completions(completions_menu) => {
18849                assert_eq!(
18850                    completions_menu
18851                        .entries
18852                        .borrow()
18853                        .iter()
18854                        .map(|mat| mat.string.clone())
18855                        .collect::<Vec<String>>(),
18856                    items
18857                        .iter()
18858                        .map(|completion| completion.label.clone())
18859                        .collect::<Vec<String>>()
18860                );
18861            }
18862            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18863        }
18864    });
18865    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18866    // with 4 from the end.
18867    assert_eq!(
18868        *resolved_items.lock(),
18869        [&items[0..16], &items[items.len() - 4..items.len()]]
18870            .concat()
18871            .iter()
18872            .cloned()
18873            .map(|mut item| {
18874                if item.data.is_none() {
18875                    item.data = Some(default_data.clone());
18876                }
18877                item
18878            })
18879            .collect::<Vec<lsp::CompletionItem>>(),
18880        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18881    );
18882    resolved_items.lock().clear();
18883
18884    cx.update_editor(|editor, window, cx| {
18885        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18886    });
18887    cx.run_until_parked();
18888    // Completions that have already been resolved are skipped.
18889    assert_eq!(
18890        *resolved_items.lock(),
18891        items[items.len() - 17..items.len() - 4]
18892            .iter()
18893            .cloned()
18894            .map(|mut item| {
18895                if item.data.is_none() {
18896                    item.data = Some(default_data.clone());
18897                }
18898                item
18899            })
18900            .collect::<Vec<lsp::CompletionItem>>()
18901    );
18902    resolved_items.lock().clear();
18903}
18904
18905#[gpui::test]
18906async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18907    init_test(cx, |_| {});
18908
18909    let mut cx = EditorLspTestContext::new(
18910        Language::new(
18911            LanguageConfig {
18912                matcher: LanguageMatcher {
18913                    path_suffixes: vec!["jsx".into()],
18914                    ..Default::default()
18915                },
18916                overrides: [(
18917                    "element".into(),
18918                    LanguageConfigOverride {
18919                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18920                        ..Default::default()
18921                    },
18922                )]
18923                .into_iter()
18924                .collect(),
18925                ..Default::default()
18926            },
18927            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18928        )
18929        .with_override_query("(jsx_self_closing_element) @element")
18930        .unwrap(),
18931        lsp::ServerCapabilities {
18932            completion_provider: Some(lsp::CompletionOptions {
18933                trigger_characters: Some(vec![":".to_string()]),
18934                ..Default::default()
18935            }),
18936            ..Default::default()
18937        },
18938        cx,
18939    )
18940    .await;
18941
18942    cx.lsp
18943        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18944            Ok(Some(lsp::CompletionResponse::Array(vec![
18945                lsp::CompletionItem {
18946                    label: "bg-blue".into(),
18947                    ..Default::default()
18948                },
18949                lsp::CompletionItem {
18950                    label: "bg-red".into(),
18951                    ..Default::default()
18952                },
18953                lsp::CompletionItem {
18954                    label: "bg-yellow".into(),
18955                    ..Default::default()
18956                },
18957            ])))
18958        });
18959
18960    cx.set_state(r#"<p class="bgˇ" />"#);
18961
18962    // Trigger completion when typing a dash, because the dash is an extra
18963    // word character in the 'element' scope, which contains the cursor.
18964    cx.simulate_keystroke("-");
18965    cx.executor().run_until_parked();
18966    cx.update_editor(|editor, _, _| {
18967        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18968        {
18969            assert_eq!(
18970                completion_menu_entries(menu),
18971                &["bg-blue", "bg-red", "bg-yellow"]
18972            );
18973        } else {
18974            panic!("expected completion menu to be open");
18975        }
18976    });
18977
18978    cx.simulate_keystroke("l");
18979    cx.executor().run_until_parked();
18980    cx.update_editor(|editor, _, _| {
18981        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18982        {
18983            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18984        } else {
18985            panic!("expected completion menu to be open");
18986        }
18987    });
18988
18989    // When filtering completions, consider the character after the '-' to
18990    // be the start of a subword.
18991    cx.set_state(r#"<p class="yelˇ" />"#);
18992    cx.simulate_keystroke("l");
18993    cx.executor().run_until_parked();
18994    cx.update_editor(|editor, _, _| {
18995        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18996        {
18997            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18998        } else {
18999            panic!("expected completion menu to be open");
19000        }
19001    });
19002}
19003
19004fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
19005    let entries = menu.entries.borrow();
19006    entries.iter().map(|mat| mat.string.clone()).collect()
19007}
19008
19009#[gpui::test]
19010async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
19011    init_test(cx, |settings| {
19012        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19013    });
19014
19015    let fs = FakeFs::new(cx.executor());
19016    fs.insert_file(path!("/file.ts"), Default::default()).await;
19017
19018    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
19019    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19020
19021    language_registry.add(Arc::new(Language::new(
19022        LanguageConfig {
19023            name: "TypeScript".into(),
19024            matcher: LanguageMatcher {
19025                path_suffixes: vec!["ts".to_string()],
19026                ..Default::default()
19027            },
19028            ..Default::default()
19029        },
19030        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19031    )));
19032    update_test_language_settings(cx, |settings| {
19033        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19034    });
19035
19036    let test_plugin = "test_plugin";
19037    let _ = language_registry.register_fake_lsp(
19038        "TypeScript",
19039        FakeLspAdapter {
19040            prettier_plugins: vec![test_plugin],
19041            ..Default::default()
19042        },
19043    );
19044
19045    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19046    let buffer = project
19047        .update(cx, |project, cx| {
19048            project.open_local_buffer(path!("/file.ts"), cx)
19049        })
19050        .await
19051        .unwrap();
19052
19053    let buffer_text = "one\ntwo\nthree\n";
19054    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19055    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19056    editor.update_in(cx, |editor, window, cx| {
19057        editor.set_text(buffer_text, window, cx)
19058    });
19059
19060    editor
19061        .update_in(cx, |editor, window, cx| {
19062            editor.perform_format(
19063                project.clone(),
19064                FormatTrigger::Manual,
19065                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19066                window,
19067                cx,
19068            )
19069        })
19070        .unwrap()
19071        .await;
19072    assert_eq!(
19073        editor.update(cx, |editor, cx| editor.text(cx)),
19074        buffer_text.to_string() + prettier_format_suffix,
19075        "Test prettier formatting was not applied to the original buffer text",
19076    );
19077
19078    update_test_language_settings(cx, |settings| {
19079        settings.defaults.formatter = Some(FormatterList::default())
19080    });
19081    let format = editor.update_in(cx, |editor, window, cx| {
19082        editor.perform_format(
19083            project.clone(),
19084            FormatTrigger::Manual,
19085            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19086            window,
19087            cx,
19088        )
19089    });
19090    format.await.unwrap();
19091    assert_eq!(
19092        editor.update(cx, |editor, cx| editor.text(cx)),
19093        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
19094        "Autoformatting (via test prettier) was not applied to the original buffer text",
19095    );
19096}
19097
19098#[gpui::test]
19099async fn test_document_format_with_prettier_explicit_language(cx: &mut TestAppContext) {
19100    init_test(cx, |settings| {
19101        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
19102    });
19103
19104    let fs = FakeFs::new(cx.executor());
19105    fs.insert_file(path!("/file.settings"), Default::default())
19106        .await;
19107
19108    let project = Project::test(fs, [path!("/file.settings").as_ref()], cx).await;
19109    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
19110
19111    let ts_lang = Arc::new(Language::new(
19112        LanguageConfig {
19113            name: "TypeScript".into(),
19114            matcher: LanguageMatcher {
19115                path_suffixes: vec!["ts".to_string()],
19116                ..LanguageMatcher::default()
19117            },
19118            prettier_parser_name: Some("typescript".to_string()),
19119            ..LanguageConfig::default()
19120        },
19121        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
19122    ));
19123
19124    language_registry.add(ts_lang.clone());
19125
19126    update_test_language_settings(cx, |settings| {
19127        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
19128    });
19129
19130    let test_plugin = "test_plugin";
19131    let _ = language_registry.register_fake_lsp(
19132        "TypeScript",
19133        FakeLspAdapter {
19134            prettier_plugins: vec![test_plugin],
19135            ..Default::default()
19136        },
19137    );
19138
19139    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
19140    let buffer = project
19141        .update(cx, |project, cx| {
19142            project.open_local_buffer(path!("/file.settings"), cx)
19143        })
19144        .await
19145        .unwrap();
19146
19147    project.update(cx, |project, cx| {
19148        project.set_language_for_buffer(&buffer, ts_lang, cx)
19149    });
19150
19151    let buffer_text = "one\ntwo\nthree\n";
19152    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
19153    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
19154    editor.update_in(cx, |editor, window, cx| {
19155        editor.set_text(buffer_text, window, cx)
19156    });
19157
19158    editor
19159        .update_in(cx, |editor, window, cx| {
19160            editor.perform_format(
19161                project.clone(),
19162                FormatTrigger::Manual,
19163                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19164                window,
19165                cx,
19166            )
19167        })
19168        .unwrap()
19169        .await;
19170    assert_eq!(
19171        editor.update(cx, |editor, cx| editor.text(cx)),
19172        buffer_text.to_string() + prettier_format_suffix + "\ntypescript",
19173        "Test prettier formatting was not applied to the original buffer text",
19174    );
19175
19176    update_test_language_settings(cx, |settings| {
19177        settings.defaults.formatter = Some(FormatterList::default())
19178    });
19179    let format = editor.update_in(cx, |editor, window, cx| {
19180        editor.perform_format(
19181            project.clone(),
19182            FormatTrigger::Manual,
19183            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
19184            window,
19185            cx,
19186        )
19187    });
19188    format.await.unwrap();
19189
19190    assert_eq!(
19191        editor.update(cx, |editor, cx| editor.text(cx)),
19192        buffer_text.to_string()
19193            + prettier_format_suffix
19194            + "\ntypescript\n"
19195            + prettier_format_suffix
19196            + "\ntypescript",
19197        "Autoformatting (via test prettier) was not applied to the original buffer text",
19198    );
19199}
19200
19201#[gpui::test]
19202async fn test_addition_reverts(cx: &mut TestAppContext) {
19203    init_test(cx, |_| {});
19204    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19205    let base_text = indoc! {r#"
19206        struct Row;
19207        struct Row1;
19208        struct Row2;
19209
19210        struct Row4;
19211        struct Row5;
19212        struct Row6;
19213
19214        struct Row8;
19215        struct Row9;
19216        struct Row10;"#};
19217
19218    // When addition hunks are not adjacent to carets, no hunk revert is performed
19219    assert_hunk_revert(
19220        indoc! {r#"struct Row;
19221                   struct Row1;
19222                   struct Row1.1;
19223                   struct Row1.2;
19224                   struct Row2;ˇ
19225
19226                   struct Row4;
19227                   struct Row5;
19228                   struct Row6;
19229
19230                   struct Row8;
19231                   ˇstruct Row9;
19232                   struct Row9.1;
19233                   struct Row9.2;
19234                   struct Row9.3;
19235                   struct Row10;"#},
19236        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19237        indoc! {r#"struct Row;
19238                   struct Row1;
19239                   struct Row1.1;
19240                   struct Row1.2;
19241                   struct Row2;ˇ
19242
19243                   struct Row4;
19244                   struct Row5;
19245                   struct Row6;
19246
19247                   struct Row8;
19248                   ˇstruct Row9;
19249                   struct Row9.1;
19250                   struct Row9.2;
19251                   struct Row9.3;
19252                   struct Row10;"#},
19253        base_text,
19254        &mut cx,
19255    );
19256    // Same for selections
19257    assert_hunk_revert(
19258        indoc! {r#"struct Row;
19259                   struct Row1;
19260                   struct Row2;
19261                   struct Row2.1;
19262                   struct Row2.2;
19263                   «ˇ
19264                   struct Row4;
19265                   struct» Row5;
19266                   «struct Row6;
19267                   ˇ»
19268                   struct Row9.1;
19269                   struct Row9.2;
19270                   struct Row9.3;
19271                   struct Row8;
19272                   struct Row9;
19273                   struct Row10;"#},
19274        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19275        indoc! {r#"struct Row;
19276                   struct Row1;
19277                   struct Row2;
19278                   struct Row2.1;
19279                   struct Row2.2;
19280                   «ˇ
19281                   struct Row4;
19282                   struct» Row5;
19283                   «struct Row6;
19284                   ˇ»
19285                   struct Row9.1;
19286                   struct Row9.2;
19287                   struct Row9.3;
19288                   struct Row8;
19289                   struct Row9;
19290                   struct Row10;"#},
19291        base_text,
19292        &mut cx,
19293    );
19294
19295    // When carets and selections intersect the addition hunks, those are reverted.
19296    // Adjacent carets got merged.
19297    assert_hunk_revert(
19298        indoc! {r#"struct Row;
19299                   ˇ// something on the top
19300                   struct Row1;
19301                   struct Row2;
19302                   struct Roˇw3.1;
19303                   struct Row2.2;
19304                   struct Row2.3;ˇ
19305
19306                   struct Row4;
19307                   struct ˇRow5.1;
19308                   struct Row5.2;
19309                   struct «Rowˇ»5.3;
19310                   struct Row5;
19311                   struct Row6;
19312                   ˇ
19313                   struct Row9.1;
19314                   struct «Rowˇ»9.2;
19315                   struct «ˇRow»9.3;
19316                   struct Row8;
19317                   struct Row9;
19318                   «ˇ// something on bottom»
19319                   struct Row10;"#},
19320        vec![
19321            DiffHunkStatusKind::Added,
19322            DiffHunkStatusKind::Added,
19323            DiffHunkStatusKind::Added,
19324            DiffHunkStatusKind::Added,
19325            DiffHunkStatusKind::Added,
19326        ],
19327        indoc! {r#"struct Row;
19328                   ˇstruct Row1;
19329                   struct Row2;
19330                   ˇ
19331                   struct Row4;
19332                   ˇstruct Row5;
19333                   struct Row6;
19334                   ˇ
19335                   ˇstruct Row8;
19336                   struct Row9;
19337                   ˇstruct Row10;"#},
19338        base_text,
19339        &mut cx,
19340    );
19341}
19342
19343#[gpui::test]
19344async fn test_modification_reverts(cx: &mut TestAppContext) {
19345    init_test(cx, |_| {});
19346    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19347    let base_text = indoc! {r#"
19348        struct Row;
19349        struct Row1;
19350        struct Row2;
19351
19352        struct Row4;
19353        struct Row5;
19354        struct Row6;
19355
19356        struct Row8;
19357        struct Row9;
19358        struct Row10;"#};
19359
19360    // Modification hunks behave the same as the addition ones.
19361    assert_hunk_revert(
19362        indoc! {r#"struct Row;
19363                   struct Row1;
19364                   struct Row33;
19365                   ˇ
19366                   struct Row4;
19367                   struct Row5;
19368                   struct Row6;
19369                   ˇ
19370                   struct Row99;
19371                   struct Row9;
19372                   struct Row10;"#},
19373        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19374        indoc! {r#"struct Row;
19375                   struct Row1;
19376                   struct Row33;
19377                   ˇ
19378                   struct Row4;
19379                   struct Row5;
19380                   struct Row6;
19381                   ˇ
19382                   struct Row99;
19383                   struct Row9;
19384                   struct Row10;"#},
19385        base_text,
19386        &mut cx,
19387    );
19388    assert_hunk_revert(
19389        indoc! {r#"struct Row;
19390                   struct Row1;
19391                   struct Row33;
19392                   «ˇ
19393                   struct Row4;
19394                   struct» Row5;
19395                   «struct Row6;
19396                   ˇ»
19397                   struct Row99;
19398                   struct Row9;
19399                   struct Row10;"#},
19400        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19401        indoc! {r#"struct Row;
19402                   struct Row1;
19403                   struct Row33;
19404                   «ˇ
19405                   struct Row4;
19406                   struct» Row5;
19407                   «struct Row6;
19408                   ˇ»
19409                   struct Row99;
19410                   struct Row9;
19411                   struct Row10;"#},
19412        base_text,
19413        &mut cx,
19414    );
19415
19416    assert_hunk_revert(
19417        indoc! {r#"ˇstruct Row1.1;
19418                   struct Row1;
19419                   «ˇstr»uct Row22;
19420
19421                   struct ˇRow44;
19422                   struct Row5;
19423                   struct «Rˇ»ow66;ˇ
19424
19425                   «struˇ»ct Row88;
19426                   struct Row9;
19427                   struct Row1011;ˇ"#},
19428        vec![
19429            DiffHunkStatusKind::Modified,
19430            DiffHunkStatusKind::Modified,
19431            DiffHunkStatusKind::Modified,
19432            DiffHunkStatusKind::Modified,
19433            DiffHunkStatusKind::Modified,
19434            DiffHunkStatusKind::Modified,
19435        ],
19436        indoc! {r#"struct Row;
19437                   ˇstruct Row1;
19438                   struct Row2;
19439                   ˇ
19440                   struct Row4;
19441                   ˇstruct Row5;
19442                   struct Row6;
19443                   ˇ
19444                   struct Row8;
19445                   ˇstruct Row9;
19446                   struct Row10;ˇ"#},
19447        base_text,
19448        &mut cx,
19449    );
19450}
19451
19452#[gpui::test]
19453async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19454    init_test(cx, |_| {});
19455    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19456    let base_text = indoc! {r#"
19457        one
19458
19459        two
19460        three
19461        "#};
19462
19463    cx.set_head_text(base_text);
19464    cx.set_state("\nˇ\n");
19465    cx.executor().run_until_parked();
19466    cx.update_editor(|editor, _window, cx| {
19467        editor.expand_selected_diff_hunks(cx);
19468    });
19469    cx.executor().run_until_parked();
19470    cx.update_editor(|editor, window, cx| {
19471        editor.backspace(&Default::default(), window, cx);
19472    });
19473    cx.run_until_parked();
19474    cx.assert_state_with_diff(
19475        indoc! {r#"
19476
19477        - two
19478        - threeˇ
19479        +
19480        "#}
19481        .to_string(),
19482    );
19483}
19484
19485#[gpui::test]
19486async fn test_deletion_reverts(cx: &mut TestAppContext) {
19487    init_test(cx, |_| {});
19488    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19489    let base_text = indoc! {r#"struct Row;
19490struct Row1;
19491struct Row2;
19492
19493struct Row4;
19494struct Row5;
19495struct Row6;
19496
19497struct Row8;
19498struct Row9;
19499struct Row10;"#};
19500
19501    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19502    assert_hunk_revert(
19503        indoc! {r#"struct Row;
19504                   struct Row2;
19505
19506                   ˇstruct Row4;
19507                   struct Row5;
19508                   struct Row6;
19509                   ˇ
19510                   struct Row8;
19511                   struct Row10;"#},
19512        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19513        indoc! {r#"struct Row;
19514                   struct Row2;
19515
19516                   ˇstruct Row4;
19517                   struct Row5;
19518                   struct Row6;
19519                   ˇ
19520                   struct Row8;
19521                   struct Row10;"#},
19522        base_text,
19523        &mut cx,
19524    );
19525    assert_hunk_revert(
19526        indoc! {r#"struct Row;
19527                   struct Row2;
19528
19529                   «ˇstruct Row4;
19530                   struct» Row5;
19531                   «struct Row6;
19532                   ˇ»
19533                   struct Row8;
19534                   struct Row10;"#},
19535        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19536        indoc! {r#"struct Row;
19537                   struct Row2;
19538
19539                   «ˇstruct Row4;
19540                   struct» Row5;
19541                   «struct Row6;
19542                   ˇ»
19543                   struct Row8;
19544                   struct Row10;"#},
19545        base_text,
19546        &mut cx,
19547    );
19548
19549    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19550    assert_hunk_revert(
19551        indoc! {r#"struct Row;
19552                   ˇstruct Row2;
19553
19554                   struct Row4;
19555                   struct Row5;
19556                   struct Row6;
19557
19558                   struct Row8;ˇ
19559                   struct Row10;"#},
19560        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19561        indoc! {r#"struct Row;
19562                   struct Row1;
19563                   ˇstruct Row2;
19564
19565                   struct Row4;
19566                   struct Row5;
19567                   struct Row6;
19568
19569                   struct Row8;ˇ
19570                   struct Row9;
19571                   struct Row10;"#},
19572        base_text,
19573        &mut cx,
19574    );
19575    assert_hunk_revert(
19576        indoc! {r#"struct Row;
19577                   struct Row2«ˇ;
19578                   struct Row4;
19579                   struct» Row5;
19580                   «struct Row6;
19581
19582                   struct Row8;ˇ»
19583                   struct Row10;"#},
19584        vec![
19585            DiffHunkStatusKind::Deleted,
19586            DiffHunkStatusKind::Deleted,
19587            DiffHunkStatusKind::Deleted,
19588        ],
19589        indoc! {r#"struct Row;
19590                   struct Row1;
19591                   struct Row2«ˇ;
19592
19593                   struct Row4;
19594                   struct» Row5;
19595                   «struct Row6;
19596
19597                   struct Row8;ˇ»
19598                   struct Row9;
19599                   struct Row10;"#},
19600        base_text,
19601        &mut cx,
19602    );
19603}
19604
19605#[gpui::test]
19606async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19607    init_test(cx, |_| {});
19608
19609    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19610    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19611    let base_text_3 =
19612        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19613
19614    let text_1 = edit_first_char_of_every_line(base_text_1);
19615    let text_2 = edit_first_char_of_every_line(base_text_2);
19616    let text_3 = edit_first_char_of_every_line(base_text_3);
19617
19618    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19619    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19620    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19621
19622    let multibuffer = cx.new(|cx| {
19623        let mut multibuffer = MultiBuffer::new(ReadWrite);
19624        multibuffer.push_excerpts(
19625            buffer_1.clone(),
19626            [
19627                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19628                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19629                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19630            ],
19631            cx,
19632        );
19633        multibuffer.push_excerpts(
19634            buffer_2.clone(),
19635            [
19636                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19637                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19638                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19639            ],
19640            cx,
19641        );
19642        multibuffer.push_excerpts(
19643            buffer_3.clone(),
19644            [
19645                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19646                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19647                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19648            ],
19649            cx,
19650        );
19651        multibuffer
19652    });
19653
19654    let fs = FakeFs::new(cx.executor());
19655    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19656    let (editor, cx) = cx
19657        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19658    editor.update_in(cx, |editor, _window, cx| {
19659        for (buffer, diff_base) in [
19660            (buffer_1.clone(), base_text_1),
19661            (buffer_2.clone(), base_text_2),
19662            (buffer_3.clone(), base_text_3),
19663        ] {
19664            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19665            editor
19666                .buffer
19667                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19668        }
19669    });
19670    cx.executor().run_until_parked();
19671
19672    editor.update_in(cx, |editor, window, cx| {
19673        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}");
19674        editor.select_all(&SelectAll, window, cx);
19675        editor.git_restore(&Default::default(), window, cx);
19676    });
19677    cx.executor().run_until_parked();
19678
19679    // When all ranges are selected, all buffer hunks are reverted.
19680    editor.update(cx, |editor, cx| {
19681        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");
19682    });
19683    buffer_1.update(cx, |buffer, _| {
19684        assert_eq!(buffer.text(), base_text_1);
19685    });
19686    buffer_2.update(cx, |buffer, _| {
19687        assert_eq!(buffer.text(), base_text_2);
19688    });
19689    buffer_3.update(cx, |buffer, _| {
19690        assert_eq!(buffer.text(), base_text_3);
19691    });
19692
19693    editor.update_in(cx, |editor, window, cx| {
19694        editor.undo(&Default::default(), window, cx);
19695    });
19696
19697    editor.update_in(cx, |editor, window, cx| {
19698        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19699            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19700        });
19701        editor.git_restore(&Default::default(), window, cx);
19702    });
19703
19704    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19705    // but not affect buffer_2 and its related excerpts.
19706    editor.update(cx, |editor, cx| {
19707        assert_eq!(
19708            editor.text(cx),
19709            "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}"
19710        );
19711    });
19712    buffer_1.update(cx, |buffer, _| {
19713        assert_eq!(buffer.text(), base_text_1);
19714    });
19715    buffer_2.update(cx, |buffer, _| {
19716        assert_eq!(
19717            buffer.text(),
19718            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19719        );
19720    });
19721    buffer_3.update(cx, |buffer, _| {
19722        assert_eq!(
19723            buffer.text(),
19724            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19725        );
19726    });
19727
19728    fn edit_first_char_of_every_line(text: &str) -> String {
19729        text.split('\n')
19730            .map(|line| format!("X{}", &line[1..]))
19731            .collect::<Vec<_>>()
19732            .join("\n")
19733    }
19734}
19735
19736#[gpui::test]
19737async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19738    init_test(cx, |_| {});
19739
19740    let cols = 4;
19741    let rows = 10;
19742    let sample_text_1 = sample_text(rows, cols, 'a');
19743    assert_eq!(
19744        sample_text_1,
19745        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19746    );
19747    let sample_text_2 = sample_text(rows, cols, 'l');
19748    assert_eq!(
19749        sample_text_2,
19750        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19751    );
19752    let sample_text_3 = sample_text(rows, cols, 'v');
19753    assert_eq!(
19754        sample_text_3,
19755        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19756    );
19757
19758    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19759    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19760    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19761
19762    let multi_buffer = cx.new(|cx| {
19763        let mut multibuffer = MultiBuffer::new(ReadWrite);
19764        multibuffer.push_excerpts(
19765            buffer_1.clone(),
19766            [
19767                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19768                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19769                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19770            ],
19771            cx,
19772        );
19773        multibuffer.push_excerpts(
19774            buffer_2.clone(),
19775            [
19776                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19777                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19778                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19779            ],
19780            cx,
19781        );
19782        multibuffer.push_excerpts(
19783            buffer_3.clone(),
19784            [
19785                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19786                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19787                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19788            ],
19789            cx,
19790        );
19791        multibuffer
19792    });
19793
19794    let fs = FakeFs::new(cx.executor());
19795    fs.insert_tree(
19796        "/a",
19797        json!({
19798            "main.rs": sample_text_1,
19799            "other.rs": sample_text_2,
19800            "lib.rs": sample_text_3,
19801        }),
19802    )
19803    .await;
19804    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19805    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19806    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19807    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19808        Editor::new(
19809            EditorMode::full(),
19810            multi_buffer,
19811            Some(project.clone()),
19812            window,
19813            cx,
19814        )
19815    });
19816    let multibuffer_item_id = workspace
19817        .update(cx, |workspace, window, cx| {
19818            assert!(
19819                workspace.active_item(cx).is_none(),
19820                "active item should be None before the first item is added"
19821            );
19822            workspace.add_item_to_active_pane(
19823                Box::new(multi_buffer_editor.clone()),
19824                None,
19825                true,
19826                window,
19827                cx,
19828            );
19829            let active_item = workspace
19830                .active_item(cx)
19831                .expect("should have an active item after adding the multi buffer");
19832            assert_eq!(
19833                active_item.buffer_kind(cx),
19834                ItemBufferKind::Multibuffer,
19835                "A multi buffer was expected to active after adding"
19836            );
19837            active_item.item_id()
19838        })
19839        .unwrap();
19840    cx.executor().run_until_parked();
19841
19842    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19843        editor.change_selections(
19844            SelectionEffects::scroll(Autoscroll::Next),
19845            window,
19846            cx,
19847            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
19848        );
19849        editor.open_excerpts(&OpenExcerpts, window, cx);
19850    });
19851    cx.executor().run_until_parked();
19852    let first_item_id = workspace
19853        .update(cx, |workspace, window, cx| {
19854            let active_item = workspace
19855                .active_item(cx)
19856                .expect("should have an active item after navigating into the 1st buffer");
19857            let first_item_id = active_item.item_id();
19858            assert_ne!(
19859                first_item_id, multibuffer_item_id,
19860                "Should navigate into the 1st buffer and activate it"
19861            );
19862            assert_eq!(
19863                active_item.buffer_kind(cx),
19864                ItemBufferKind::Singleton,
19865                "New active item should be a singleton buffer"
19866            );
19867            assert_eq!(
19868                active_item
19869                    .act_as::<Editor>(cx)
19870                    .expect("should have navigated into an editor for the 1st buffer")
19871                    .read(cx)
19872                    .text(cx),
19873                sample_text_1
19874            );
19875
19876            workspace
19877                .go_back(workspace.active_pane().downgrade(), window, cx)
19878                .detach_and_log_err(cx);
19879
19880            first_item_id
19881        })
19882        .unwrap();
19883    cx.executor().run_until_parked();
19884    workspace
19885        .update(cx, |workspace, _, cx| {
19886            let active_item = workspace
19887                .active_item(cx)
19888                .expect("should have an active item after navigating back");
19889            assert_eq!(
19890                active_item.item_id(),
19891                multibuffer_item_id,
19892                "Should navigate back to the multi buffer"
19893            );
19894            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19895        })
19896        .unwrap();
19897
19898    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19899        editor.change_selections(
19900            SelectionEffects::scroll(Autoscroll::Next),
19901            window,
19902            cx,
19903            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
19904        );
19905        editor.open_excerpts(&OpenExcerpts, window, cx);
19906    });
19907    cx.executor().run_until_parked();
19908    let second_item_id = workspace
19909        .update(cx, |workspace, window, cx| {
19910            let active_item = workspace
19911                .active_item(cx)
19912                .expect("should have an active item after navigating into the 2nd buffer");
19913            let second_item_id = active_item.item_id();
19914            assert_ne!(
19915                second_item_id, multibuffer_item_id,
19916                "Should navigate away from the multibuffer"
19917            );
19918            assert_ne!(
19919                second_item_id, first_item_id,
19920                "Should navigate into the 2nd buffer and activate it"
19921            );
19922            assert_eq!(
19923                active_item.buffer_kind(cx),
19924                ItemBufferKind::Singleton,
19925                "New active item should be a singleton buffer"
19926            );
19927            assert_eq!(
19928                active_item
19929                    .act_as::<Editor>(cx)
19930                    .expect("should have navigated into an editor")
19931                    .read(cx)
19932                    .text(cx),
19933                sample_text_2
19934            );
19935
19936            workspace
19937                .go_back(workspace.active_pane().downgrade(), window, cx)
19938                .detach_and_log_err(cx);
19939
19940            second_item_id
19941        })
19942        .unwrap();
19943    cx.executor().run_until_parked();
19944    workspace
19945        .update(cx, |workspace, _, cx| {
19946            let active_item = workspace
19947                .active_item(cx)
19948                .expect("should have an active item after navigating back from the 2nd buffer");
19949            assert_eq!(
19950                active_item.item_id(),
19951                multibuffer_item_id,
19952                "Should navigate back from the 2nd buffer to the multi buffer"
19953            );
19954            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19955        })
19956        .unwrap();
19957
19958    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19959        editor.change_selections(
19960            SelectionEffects::scroll(Autoscroll::Next),
19961            window,
19962            cx,
19963            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
19964        );
19965        editor.open_excerpts(&OpenExcerpts, window, cx);
19966    });
19967    cx.executor().run_until_parked();
19968    workspace
19969        .update(cx, |workspace, window, cx| {
19970            let active_item = workspace
19971                .active_item(cx)
19972                .expect("should have an active item after navigating into the 3rd buffer");
19973            let third_item_id = active_item.item_id();
19974            assert_ne!(
19975                third_item_id, multibuffer_item_id,
19976                "Should navigate into the 3rd buffer and activate it"
19977            );
19978            assert_ne!(third_item_id, first_item_id);
19979            assert_ne!(third_item_id, second_item_id);
19980            assert_eq!(
19981                active_item.buffer_kind(cx),
19982                ItemBufferKind::Singleton,
19983                "New active item should be a singleton buffer"
19984            );
19985            assert_eq!(
19986                active_item
19987                    .act_as::<Editor>(cx)
19988                    .expect("should have navigated into an editor")
19989                    .read(cx)
19990                    .text(cx),
19991                sample_text_3
19992            );
19993
19994            workspace
19995                .go_back(workspace.active_pane().downgrade(), window, cx)
19996                .detach_and_log_err(cx);
19997        })
19998        .unwrap();
19999    cx.executor().run_until_parked();
20000    workspace
20001        .update(cx, |workspace, _, cx| {
20002            let active_item = workspace
20003                .active_item(cx)
20004                .expect("should have an active item after navigating back from the 3rd buffer");
20005            assert_eq!(
20006                active_item.item_id(),
20007                multibuffer_item_id,
20008                "Should navigate back from the 3rd buffer to the multi buffer"
20009            );
20010            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
20011        })
20012        .unwrap();
20013}
20014
20015#[gpui::test]
20016async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20017    init_test(cx, |_| {});
20018
20019    let mut cx = EditorTestContext::new(cx).await;
20020
20021    let diff_base = r#"
20022        use some::mod;
20023
20024        const A: u32 = 42;
20025
20026        fn main() {
20027            println!("hello");
20028
20029            println!("world");
20030        }
20031        "#
20032    .unindent();
20033
20034    cx.set_state(
20035        &r#"
20036        use some::modified;
20037
20038        ˇ
20039        fn main() {
20040            println!("hello there");
20041
20042            println!("around the");
20043            println!("world");
20044        }
20045        "#
20046        .unindent(),
20047    );
20048
20049    cx.set_head_text(&diff_base);
20050    executor.run_until_parked();
20051
20052    cx.update_editor(|editor, window, cx| {
20053        editor.go_to_next_hunk(&GoToHunk, window, cx);
20054        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20055    });
20056    executor.run_until_parked();
20057    cx.assert_state_with_diff(
20058        r#"
20059          use some::modified;
20060
20061
20062          fn main() {
20063        -     println!("hello");
20064        + ˇ    println!("hello there");
20065
20066              println!("around the");
20067              println!("world");
20068          }
20069        "#
20070        .unindent(),
20071    );
20072
20073    cx.update_editor(|editor, window, cx| {
20074        for _ in 0..2 {
20075            editor.go_to_next_hunk(&GoToHunk, window, cx);
20076            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20077        }
20078    });
20079    executor.run_until_parked();
20080    cx.assert_state_with_diff(
20081        r#"
20082        - use some::mod;
20083        + ˇuse some::modified;
20084
20085
20086          fn main() {
20087        -     println!("hello");
20088        +     println!("hello there");
20089
20090        +     println!("around the");
20091              println!("world");
20092          }
20093        "#
20094        .unindent(),
20095    );
20096
20097    cx.update_editor(|editor, window, cx| {
20098        editor.go_to_next_hunk(&GoToHunk, window, cx);
20099        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20100    });
20101    executor.run_until_parked();
20102    cx.assert_state_with_diff(
20103        r#"
20104        - use some::mod;
20105        + use some::modified;
20106
20107        - const A: u32 = 42;
20108          ˇ
20109          fn main() {
20110        -     println!("hello");
20111        +     println!("hello there");
20112
20113        +     println!("around the");
20114              println!("world");
20115          }
20116        "#
20117        .unindent(),
20118    );
20119
20120    cx.update_editor(|editor, window, cx| {
20121        editor.cancel(&Cancel, window, cx);
20122    });
20123
20124    cx.assert_state_with_diff(
20125        r#"
20126          use some::modified;
20127
20128          ˇ
20129          fn main() {
20130              println!("hello there");
20131
20132              println!("around the");
20133              println!("world");
20134          }
20135        "#
20136        .unindent(),
20137    );
20138}
20139
20140#[gpui::test]
20141async fn test_diff_base_change_with_expanded_diff_hunks(
20142    executor: BackgroundExecutor,
20143    cx: &mut TestAppContext,
20144) {
20145    init_test(cx, |_| {});
20146
20147    let mut cx = EditorTestContext::new(cx).await;
20148
20149    let diff_base = r#"
20150        use some::mod1;
20151        use some::mod2;
20152
20153        const A: u32 = 42;
20154        const B: u32 = 42;
20155        const C: u32 = 42;
20156
20157        fn main() {
20158            println!("hello");
20159
20160            println!("world");
20161        }
20162        "#
20163    .unindent();
20164
20165    cx.set_state(
20166        &r#"
20167        use some::mod2;
20168
20169        const A: u32 = 42;
20170        const C: u32 = 42;
20171
20172        fn main(ˇ) {
20173            //println!("hello");
20174
20175            println!("world");
20176            //
20177            //
20178        }
20179        "#
20180        .unindent(),
20181    );
20182
20183    cx.set_head_text(&diff_base);
20184    executor.run_until_parked();
20185
20186    cx.update_editor(|editor, window, cx| {
20187        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20188    });
20189    executor.run_until_parked();
20190    cx.assert_state_with_diff(
20191        r#"
20192        - use some::mod1;
20193          use some::mod2;
20194
20195          const A: u32 = 42;
20196        - const B: u32 = 42;
20197          const C: u32 = 42;
20198
20199          fn main(ˇ) {
20200        -     println!("hello");
20201        +     //println!("hello");
20202
20203              println!("world");
20204        +     //
20205        +     //
20206          }
20207        "#
20208        .unindent(),
20209    );
20210
20211    cx.set_head_text("new diff base!");
20212    executor.run_until_parked();
20213    cx.assert_state_with_diff(
20214        r#"
20215        - new diff base!
20216        + use some::mod2;
20217        +
20218        + const A: u32 = 42;
20219        + const C: u32 = 42;
20220        +
20221        + fn main(ˇ) {
20222        +     //println!("hello");
20223        +
20224        +     println!("world");
20225        +     //
20226        +     //
20227        + }
20228        "#
20229        .unindent(),
20230    );
20231}
20232
20233#[gpui::test]
20234async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
20235    init_test(cx, |_| {});
20236
20237    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20238    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
20239    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20240    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
20241    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
20242    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
20243
20244    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
20245    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
20246    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
20247
20248    let multi_buffer = cx.new(|cx| {
20249        let mut multibuffer = MultiBuffer::new(ReadWrite);
20250        multibuffer.push_excerpts(
20251            buffer_1.clone(),
20252            [
20253                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20254                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20255                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20256            ],
20257            cx,
20258        );
20259        multibuffer.push_excerpts(
20260            buffer_2.clone(),
20261            [
20262                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20263                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20264                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20265            ],
20266            cx,
20267        );
20268        multibuffer.push_excerpts(
20269            buffer_3.clone(),
20270            [
20271                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
20272                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
20273                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20274            ],
20275            cx,
20276        );
20277        multibuffer
20278    });
20279
20280    let editor =
20281        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20282    editor
20283        .update(cx, |editor, _window, cx| {
20284            for (buffer, diff_base) in [
20285                (buffer_1.clone(), file_1_old),
20286                (buffer_2.clone(), file_2_old),
20287                (buffer_3.clone(), file_3_old),
20288            ] {
20289                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20290                editor
20291                    .buffer
20292                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20293            }
20294        })
20295        .unwrap();
20296
20297    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20298    cx.run_until_parked();
20299
20300    cx.assert_editor_state(
20301        &"
20302            ˇaaa
20303            ccc
20304            ddd
20305
20306            ggg
20307            hhh
20308
20309
20310            lll
20311            mmm
20312            NNN
20313
20314            qqq
20315            rrr
20316
20317            uuu
20318            111
20319            222
20320            333
20321
20322            666
20323            777
20324
20325            000
20326            !!!"
20327        .unindent(),
20328    );
20329
20330    cx.update_editor(|editor, window, cx| {
20331        editor.select_all(&SelectAll, window, cx);
20332        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20333    });
20334    cx.executor().run_until_parked();
20335
20336    cx.assert_state_with_diff(
20337        "
20338            «aaa
20339          - bbb
20340            ccc
20341            ddd
20342
20343            ggg
20344            hhh
20345
20346
20347            lll
20348            mmm
20349          - nnn
20350          + NNN
20351
20352            qqq
20353            rrr
20354
20355            uuu
20356            111
20357            222
20358            333
20359
20360          + 666
20361            777
20362
20363            000
20364            !!!ˇ»"
20365            .unindent(),
20366    );
20367}
20368
20369#[gpui::test]
20370async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20371    init_test(cx, |_| {});
20372
20373    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20374    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20375
20376    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20377    let multi_buffer = cx.new(|cx| {
20378        let mut multibuffer = MultiBuffer::new(ReadWrite);
20379        multibuffer.push_excerpts(
20380            buffer.clone(),
20381            [
20382                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20383                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20384                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20385            ],
20386            cx,
20387        );
20388        multibuffer
20389    });
20390
20391    let editor =
20392        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20393    editor
20394        .update(cx, |editor, _window, cx| {
20395            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20396            editor
20397                .buffer
20398                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20399        })
20400        .unwrap();
20401
20402    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20403    cx.run_until_parked();
20404
20405    cx.update_editor(|editor, window, cx| {
20406        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20407    });
20408    cx.executor().run_until_parked();
20409
20410    // When the start of a hunk coincides with the start of its excerpt,
20411    // the hunk is expanded. When the start of a hunk is earlier than
20412    // the start of its excerpt, the hunk is not expanded.
20413    cx.assert_state_with_diff(
20414        "
20415            ˇaaa
20416          - bbb
20417          + BBB
20418
20419          - ddd
20420          - eee
20421          + DDD
20422          + EEE
20423            fff
20424
20425            iii
20426        "
20427        .unindent(),
20428    );
20429}
20430
20431#[gpui::test]
20432async fn test_edits_around_expanded_insertion_hunks(
20433    executor: BackgroundExecutor,
20434    cx: &mut TestAppContext,
20435) {
20436    init_test(cx, |_| {});
20437
20438    let mut cx = EditorTestContext::new(cx).await;
20439
20440    let diff_base = r#"
20441        use some::mod1;
20442        use some::mod2;
20443
20444        const A: u32 = 42;
20445
20446        fn main() {
20447            println!("hello");
20448
20449            println!("world");
20450        }
20451        "#
20452    .unindent();
20453    executor.run_until_parked();
20454    cx.set_state(
20455        &r#"
20456        use some::mod1;
20457        use some::mod2;
20458
20459        const A: u32 = 42;
20460        const B: u32 = 42;
20461        const C: u32 = 42;
20462        ˇ
20463
20464        fn main() {
20465            println!("hello");
20466
20467            println!("world");
20468        }
20469        "#
20470        .unindent(),
20471    );
20472
20473    cx.set_head_text(&diff_base);
20474    executor.run_until_parked();
20475
20476    cx.update_editor(|editor, window, cx| {
20477        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20478    });
20479    executor.run_until_parked();
20480
20481    cx.assert_state_with_diff(
20482        r#"
20483        use some::mod1;
20484        use some::mod2;
20485
20486        const A: u32 = 42;
20487      + const B: u32 = 42;
20488      + const C: u32 = 42;
20489      + ˇ
20490
20491        fn main() {
20492            println!("hello");
20493
20494            println!("world");
20495        }
20496      "#
20497        .unindent(),
20498    );
20499
20500    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20501    executor.run_until_parked();
20502
20503    cx.assert_state_with_diff(
20504        r#"
20505        use some::mod1;
20506        use some::mod2;
20507
20508        const A: u32 = 42;
20509      + const B: u32 = 42;
20510      + const C: u32 = 42;
20511      + const D: u32 = 42;
20512      + ˇ
20513
20514        fn main() {
20515            println!("hello");
20516
20517            println!("world");
20518        }
20519      "#
20520        .unindent(),
20521    );
20522
20523    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20524    executor.run_until_parked();
20525
20526    cx.assert_state_with_diff(
20527        r#"
20528        use some::mod1;
20529        use some::mod2;
20530
20531        const A: u32 = 42;
20532      + const B: u32 = 42;
20533      + const C: u32 = 42;
20534      + const D: u32 = 42;
20535      + const E: u32 = 42;
20536      + ˇ
20537
20538        fn main() {
20539            println!("hello");
20540
20541            println!("world");
20542        }
20543      "#
20544        .unindent(),
20545    );
20546
20547    cx.update_editor(|editor, window, cx| {
20548        editor.delete_line(&DeleteLine, window, cx);
20549    });
20550    executor.run_until_parked();
20551
20552    cx.assert_state_with_diff(
20553        r#"
20554        use some::mod1;
20555        use some::mod2;
20556
20557        const A: u32 = 42;
20558      + const B: u32 = 42;
20559      + const C: u32 = 42;
20560      + const D: u32 = 42;
20561      + const E: u32 = 42;
20562        ˇ
20563        fn main() {
20564            println!("hello");
20565
20566            println!("world");
20567        }
20568      "#
20569        .unindent(),
20570    );
20571
20572    cx.update_editor(|editor, window, cx| {
20573        editor.move_up(&MoveUp, window, cx);
20574        editor.delete_line(&DeleteLine, window, cx);
20575        editor.move_up(&MoveUp, window, cx);
20576        editor.delete_line(&DeleteLine, window, cx);
20577        editor.move_up(&MoveUp, window, cx);
20578        editor.delete_line(&DeleteLine, window, cx);
20579    });
20580    executor.run_until_parked();
20581    cx.assert_state_with_diff(
20582        r#"
20583        use some::mod1;
20584        use some::mod2;
20585
20586        const A: u32 = 42;
20587      + const B: u32 = 42;
20588        ˇ
20589        fn main() {
20590            println!("hello");
20591
20592            println!("world");
20593        }
20594      "#
20595        .unindent(),
20596    );
20597
20598    cx.update_editor(|editor, window, cx| {
20599        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20600        editor.delete_line(&DeleteLine, window, cx);
20601    });
20602    executor.run_until_parked();
20603    cx.assert_state_with_diff(
20604        r#"
20605        ˇ
20606        fn main() {
20607            println!("hello");
20608
20609            println!("world");
20610        }
20611      "#
20612        .unindent(),
20613    );
20614}
20615
20616#[gpui::test]
20617async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20618    init_test(cx, |_| {});
20619
20620    let mut cx = EditorTestContext::new(cx).await;
20621    cx.set_head_text(indoc! { "
20622        one
20623        two
20624        three
20625        four
20626        five
20627        "
20628    });
20629    cx.set_state(indoc! { "
20630        one
20631        ˇthree
20632        five
20633    "});
20634    cx.run_until_parked();
20635    cx.update_editor(|editor, window, cx| {
20636        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20637    });
20638    cx.assert_state_with_diff(
20639        indoc! { "
20640        one
20641      - two
20642        ˇthree
20643      - four
20644        five
20645    "}
20646        .to_string(),
20647    );
20648    cx.update_editor(|editor, window, cx| {
20649        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20650    });
20651
20652    cx.assert_state_with_diff(
20653        indoc! { "
20654        one
20655        ˇthree
20656        five
20657    "}
20658        .to_string(),
20659    );
20660
20661    cx.set_state(indoc! { "
20662        one
20663        ˇTWO
20664        three
20665        four
20666        five
20667    "});
20668    cx.run_until_parked();
20669    cx.update_editor(|editor, window, cx| {
20670        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20671    });
20672
20673    cx.assert_state_with_diff(
20674        indoc! { "
20675            one
20676          - two
20677          + ˇTWO
20678            three
20679            four
20680            five
20681        "}
20682        .to_string(),
20683    );
20684    cx.update_editor(|editor, window, cx| {
20685        editor.move_up(&Default::default(), window, cx);
20686        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20687    });
20688    cx.assert_state_with_diff(
20689        indoc! { "
20690            one
20691            ˇTWO
20692            three
20693            four
20694            five
20695        "}
20696        .to_string(),
20697    );
20698}
20699
20700#[gpui::test]
20701async fn test_edits_around_expanded_deletion_hunks(
20702    executor: BackgroundExecutor,
20703    cx: &mut TestAppContext,
20704) {
20705    init_test(cx, |_| {});
20706
20707    let mut cx = EditorTestContext::new(cx).await;
20708
20709    let diff_base = r#"
20710        use some::mod1;
20711        use some::mod2;
20712
20713        const A: u32 = 42;
20714        const B: u32 = 42;
20715        const C: u32 = 42;
20716
20717
20718        fn main() {
20719            println!("hello");
20720
20721            println!("world");
20722        }
20723    "#
20724    .unindent();
20725    executor.run_until_parked();
20726    cx.set_state(
20727        &r#"
20728        use some::mod1;
20729        use some::mod2;
20730
20731        ˇconst B: u32 = 42;
20732        const C: u32 = 42;
20733
20734
20735        fn main() {
20736            println!("hello");
20737
20738            println!("world");
20739        }
20740        "#
20741        .unindent(),
20742    );
20743
20744    cx.set_head_text(&diff_base);
20745    executor.run_until_parked();
20746
20747    cx.update_editor(|editor, window, cx| {
20748        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20749    });
20750    executor.run_until_parked();
20751
20752    cx.assert_state_with_diff(
20753        r#"
20754        use some::mod1;
20755        use some::mod2;
20756
20757      - const A: u32 = 42;
20758        ˇconst B: u32 = 42;
20759        const C: u32 = 42;
20760
20761
20762        fn main() {
20763            println!("hello");
20764
20765            println!("world");
20766        }
20767      "#
20768        .unindent(),
20769    );
20770
20771    cx.update_editor(|editor, window, cx| {
20772        editor.delete_line(&DeleteLine, window, cx);
20773    });
20774    executor.run_until_parked();
20775    cx.assert_state_with_diff(
20776        r#"
20777        use some::mod1;
20778        use some::mod2;
20779
20780      - const A: u32 = 42;
20781      - const B: u32 = 42;
20782        ˇconst C: u32 = 42;
20783
20784
20785        fn main() {
20786            println!("hello");
20787
20788            println!("world");
20789        }
20790      "#
20791        .unindent(),
20792    );
20793
20794    cx.update_editor(|editor, window, cx| {
20795        editor.delete_line(&DeleteLine, window, cx);
20796    });
20797    executor.run_until_parked();
20798    cx.assert_state_with_diff(
20799        r#"
20800        use some::mod1;
20801        use some::mod2;
20802
20803      - const A: u32 = 42;
20804      - const B: u32 = 42;
20805      - const C: u32 = 42;
20806        ˇ
20807
20808        fn main() {
20809            println!("hello");
20810
20811            println!("world");
20812        }
20813      "#
20814        .unindent(),
20815    );
20816
20817    cx.update_editor(|editor, window, cx| {
20818        editor.handle_input("replacement", window, cx);
20819    });
20820    executor.run_until_parked();
20821    cx.assert_state_with_diff(
20822        r#"
20823        use some::mod1;
20824        use some::mod2;
20825
20826      - const A: u32 = 42;
20827      - const B: u32 = 42;
20828      - const C: u32 = 42;
20829      -
20830      + replacementˇ
20831
20832        fn main() {
20833            println!("hello");
20834
20835            println!("world");
20836        }
20837      "#
20838        .unindent(),
20839    );
20840}
20841
20842#[gpui::test]
20843async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20844    init_test(cx, |_| {});
20845
20846    let mut cx = EditorTestContext::new(cx).await;
20847
20848    let base_text = r#"
20849        one
20850        two
20851        three
20852        four
20853        five
20854    "#
20855    .unindent();
20856    executor.run_until_parked();
20857    cx.set_state(
20858        &r#"
20859        one
20860        two
20861        fˇour
20862        five
20863        "#
20864        .unindent(),
20865    );
20866
20867    cx.set_head_text(&base_text);
20868    executor.run_until_parked();
20869
20870    cx.update_editor(|editor, window, cx| {
20871        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20872    });
20873    executor.run_until_parked();
20874
20875    cx.assert_state_with_diff(
20876        r#"
20877          one
20878          two
20879        - three
20880          fˇour
20881          five
20882        "#
20883        .unindent(),
20884    );
20885
20886    cx.update_editor(|editor, window, cx| {
20887        editor.backspace(&Backspace, window, cx);
20888        editor.backspace(&Backspace, window, cx);
20889    });
20890    executor.run_until_parked();
20891    cx.assert_state_with_diff(
20892        r#"
20893          one
20894          two
20895        - threeˇ
20896        - four
20897        + our
20898          five
20899        "#
20900        .unindent(),
20901    );
20902}
20903
20904#[gpui::test]
20905async fn test_edit_after_expanded_modification_hunk(
20906    executor: BackgroundExecutor,
20907    cx: &mut TestAppContext,
20908) {
20909    init_test(cx, |_| {});
20910
20911    let mut cx = EditorTestContext::new(cx).await;
20912
20913    let diff_base = r#"
20914        use some::mod1;
20915        use some::mod2;
20916
20917        const A: u32 = 42;
20918        const B: u32 = 42;
20919        const C: u32 = 42;
20920        const D: u32 = 42;
20921
20922
20923        fn main() {
20924            println!("hello");
20925
20926            println!("world");
20927        }"#
20928    .unindent();
20929
20930    cx.set_state(
20931        &r#"
20932        use some::mod1;
20933        use some::mod2;
20934
20935        const A: u32 = 42;
20936        const B: u32 = 42;
20937        const C: u32 = 43ˇ
20938        const D: u32 = 42;
20939
20940
20941        fn main() {
20942            println!("hello");
20943
20944            println!("world");
20945        }"#
20946        .unindent(),
20947    );
20948
20949    cx.set_head_text(&diff_base);
20950    executor.run_until_parked();
20951    cx.update_editor(|editor, window, cx| {
20952        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20953    });
20954    executor.run_until_parked();
20955
20956    cx.assert_state_with_diff(
20957        r#"
20958        use some::mod1;
20959        use some::mod2;
20960
20961        const A: u32 = 42;
20962        const B: u32 = 42;
20963      - const C: u32 = 42;
20964      + const C: u32 = 43ˇ
20965        const D: u32 = 42;
20966
20967
20968        fn main() {
20969            println!("hello");
20970
20971            println!("world");
20972        }"#
20973        .unindent(),
20974    );
20975
20976    cx.update_editor(|editor, window, cx| {
20977        editor.handle_input("\nnew_line\n", window, cx);
20978    });
20979    executor.run_until_parked();
20980
20981    cx.assert_state_with_diff(
20982        r#"
20983        use some::mod1;
20984        use some::mod2;
20985
20986        const A: u32 = 42;
20987        const B: u32 = 42;
20988      - const C: u32 = 42;
20989      + const C: u32 = 43
20990      + new_line
20991      + ˇ
20992        const D: u32 = 42;
20993
20994
20995        fn main() {
20996            println!("hello");
20997
20998            println!("world");
20999        }"#
21000        .unindent(),
21001    );
21002}
21003
21004#[gpui::test]
21005async fn test_stage_and_unstage_added_file_hunk(
21006    executor: BackgroundExecutor,
21007    cx: &mut TestAppContext,
21008) {
21009    init_test(cx, |_| {});
21010
21011    let mut cx = EditorTestContext::new(cx).await;
21012    cx.update_editor(|editor, _, cx| {
21013        editor.set_expand_all_diff_hunks(cx);
21014    });
21015
21016    let working_copy = r#"
21017            ˇfn main() {
21018                println!("hello, world!");
21019            }
21020        "#
21021    .unindent();
21022
21023    cx.set_state(&working_copy);
21024    executor.run_until_parked();
21025
21026    cx.assert_state_with_diff(
21027        r#"
21028            + ˇfn main() {
21029            +     println!("hello, world!");
21030            + }
21031        "#
21032        .unindent(),
21033    );
21034    cx.assert_index_text(None);
21035
21036    cx.update_editor(|editor, window, cx| {
21037        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21038    });
21039    executor.run_until_parked();
21040    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
21041    cx.assert_state_with_diff(
21042        r#"
21043            + ˇfn main() {
21044            +     println!("hello, world!");
21045            + }
21046        "#
21047        .unindent(),
21048    );
21049
21050    cx.update_editor(|editor, window, cx| {
21051        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21052    });
21053    executor.run_until_parked();
21054    cx.assert_index_text(None);
21055}
21056
21057async fn setup_indent_guides_editor(
21058    text: &str,
21059    cx: &mut TestAppContext,
21060) -> (BufferId, EditorTestContext) {
21061    init_test(cx, |_| {});
21062
21063    let mut cx = EditorTestContext::new(cx).await;
21064
21065    let buffer_id = cx.update_editor(|editor, window, cx| {
21066        editor.set_text(text, window, cx);
21067        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
21068
21069        buffer_ids[0]
21070    });
21071
21072    (buffer_id, cx)
21073}
21074
21075fn assert_indent_guides(
21076    range: Range<u32>,
21077    expected: Vec<IndentGuide>,
21078    active_indices: Option<Vec<usize>>,
21079    cx: &mut EditorTestContext,
21080) {
21081    let indent_guides = cx.update_editor(|editor, window, cx| {
21082        let snapshot = editor.snapshot(window, cx).display_snapshot;
21083        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
21084            editor,
21085            MultiBufferRow(range.start)..MultiBufferRow(range.end),
21086            true,
21087            &snapshot,
21088            cx,
21089        );
21090
21091        indent_guides.sort_by(|a, b| {
21092            a.depth.cmp(&b.depth).then(
21093                a.start_row
21094                    .cmp(&b.start_row)
21095                    .then(a.end_row.cmp(&b.end_row)),
21096            )
21097        });
21098        indent_guides
21099    });
21100
21101    if let Some(expected) = active_indices {
21102        let active_indices = cx.update_editor(|editor, window, cx| {
21103            let snapshot = editor.snapshot(window, cx).display_snapshot;
21104            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
21105        });
21106
21107        assert_eq!(
21108            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
21109            expected,
21110            "Active indent guide indices do not match"
21111        );
21112    }
21113
21114    assert_eq!(indent_guides, expected, "Indent guides do not match");
21115}
21116
21117fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
21118    IndentGuide {
21119        buffer_id,
21120        start_row: MultiBufferRow(start_row),
21121        end_row: MultiBufferRow(end_row),
21122        depth,
21123        tab_size: 4,
21124        settings: IndentGuideSettings {
21125            enabled: true,
21126            line_width: 1,
21127            active_line_width: 1,
21128            coloring: IndentGuideColoring::default(),
21129            background_coloring: IndentGuideBackgroundColoring::default(),
21130        },
21131    }
21132}
21133
21134#[gpui::test]
21135async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
21136    let (buffer_id, mut cx) = setup_indent_guides_editor(
21137        &"
21138        fn main() {
21139            let a = 1;
21140        }"
21141        .unindent(),
21142        cx,
21143    )
21144    .await;
21145
21146    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21147}
21148
21149#[gpui::test]
21150async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
21151    let (buffer_id, mut cx) = setup_indent_guides_editor(
21152        &"
21153        fn main() {
21154            let a = 1;
21155            let b = 2;
21156        }"
21157        .unindent(),
21158        cx,
21159    )
21160    .await;
21161
21162    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
21163}
21164
21165#[gpui::test]
21166async fn test_indent_guide_nested(cx: &mut TestAppContext) {
21167    let (buffer_id, mut cx) = setup_indent_guides_editor(
21168        &"
21169        fn main() {
21170            let a = 1;
21171            if a == 3 {
21172                let b = 2;
21173            } else {
21174                let c = 3;
21175            }
21176        }"
21177        .unindent(),
21178        cx,
21179    )
21180    .await;
21181
21182    assert_indent_guides(
21183        0..8,
21184        vec![
21185            indent_guide(buffer_id, 1, 6, 0),
21186            indent_guide(buffer_id, 3, 3, 1),
21187            indent_guide(buffer_id, 5, 5, 1),
21188        ],
21189        None,
21190        &mut cx,
21191    );
21192}
21193
21194#[gpui::test]
21195async fn test_indent_guide_tab(cx: &mut TestAppContext) {
21196    let (buffer_id, mut cx) = setup_indent_guides_editor(
21197        &"
21198        fn main() {
21199            let a = 1;
21200                let b = 2;
21201            let c = 3;
21202        }"
21203        .unindent(),
21204        cx,
21205    )
21206    .await;
21207
21208    assert_indent_guides(
21209        0..5,
21210        vec![
21211            indent_guide(buffer_id, 1, 3, 0),
21212            indent_guide(buffer_id, 2, 2, 1),
21213        ],
21214        None,
21215        &mut cx,
21216    );
21217}
21218
21219#[gpui::test]
21220async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
21221    let (buffer_id, mut cx) = setup_indent_guides_editor(
21222        &"
21223        fn main() {
21224            let a = 1;
21225
21226            let c = 3;
21227        }"
21228        .unindent(),
21229        cx,
21230    )
21231    .await;
21232
21233    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
21234}
21235
21236#[gpui::test]
21237async fn test_indent_guide_complex(cx: &mut TestAppContext) {
21238    let (buffer_id, mut cx) = setup_indent_guides_editor(
21239        &"
21240        fn main() {
21241            let a = 1;
21242
21243            let c = 3;
21244
21245            if a == 3 {
21246                let b = 2;
21247            } else {
21248                let c = 3;
21249            }
21250        }"
21251        .unindent(),
21252        cx,
21253    )
21254    .await;
21255
21256    assert_indent_guides(
21257        0..11,
21258        vec![
21259            indent_guide(buffer_id, 1, 9, 0),
21260            indent_guide(buffer_id, 6, 6, 1),
21261            indent_guide(buffer_id, 8, 8, 1),
21262        ],
21263        None,
21264        &mut cx,
21265    );
21266}
21267
21268#[gpui::test]
21269async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
21270    let (buffer_id, mut cx) = setup_indent_guides_editor(
21271        &"
21272        fn main() {
21273            let a = 1;
21274
21275            let c = 3;
21276
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        1..11,
21290        vec![
21291            indent_guide(buffer_id, 1, 9, 0),
21292            indent_guide(buffer_id, 6, 6, 1),
21293            indent_guide(buffer_id, 8, 8, 1),
21294        ],
21295        None,
21296        &mut cx,
21297    );
21298}
21299
21300#[gpui::test]
21301async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21302    let (buffer_id, mut cx) = setup_indent_guides_editor(
21303        &"
21304        fn main() {
21305            let a = 1;
21306
21307            let c = 3;
21308
21309            if a == 3 {
21310                let b = 2;
21311            } else {
21312                let c = 3;
21313            }
21314        }"
21315        .unindent(),
21316        cx,
21317    )
21318    .await;
21319
21320    assert_indent_guides(
21321        1..10,
21322        vec![
21323            indent_guide(buffer_id, 1, 9, 0),
21324            indent_guide(buffer_id, 6, 6, 1),
21325            indent_guide(buffer_id, 8, 8, 1),
21326        ],
21327        None,
21328        &mut cx,
21329    );
21330}
21331
21332#[gpui::test]
21333async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21334    let (buffer_id, mut cx) = setup_indent_guides_editor(
21335        &"
21336        fn main() {
21337            if a {
21338                b(
21339                    c,
21340                    d,
21341                )
21342            } else {
21343                e(
21344                    f
21345                )
21346            }
21347        }"
21348        .unindent(),
21349        cx,
21350    )
21351    .await;
21352
21353    assert_indent_guides(
21354        0..11,
21355        vec![
21356            indent_guide(buffer_id, 1, 10, 0),
21357            indent_guide(buffer_id, 2, 5, 1),
21358            indent_guide(buffer_id, 7, 9, 1),
21359            indent_guide(buffer_id, 3, 4, 2),
21360            indent_guide(buffer_id, 8, 8, 2),
21361        ],
21362        None,
21363        &mut cx,
21364    );
21365
21366    cx.update_editor(|editor, window, cx| {
21367        editor.fold_at(MultiBufferRow(2), window, cx);
21368        assert_eq!(
21369            editor.display_text(cx),
21370            "
21371            fn main() {
21372                if a {
21373                    b(⋯
21374                    )
21375                } else {
21376                    e(
21377                        f
21378                    )
21379                }
21380            }"
21381            .unindent()
21382        );
21383    });
21384
21385    assert_indent_guides(
21386        0..11,
21387        vec![
21388            indent_guide(buffer_id, 1, 10, 0),
21389            indent_guide(buffer_id, 2, 5, 1),
21390            indent_guide(buffer_id, 7, 9, 1),
21391            indent_guide(buffer_id, 8, 8, 2),
21392        ],
21393        None,
21394        &mut cx,
21395    );
21396}
21397
21398#[gpui::test]
21399async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21400    let (buffer_id, mut cx) = setup_indent_guides_editor(
21401        &"
21402        block1
21403            block2
21404                block3
21405                    block4
21406            block2
21407        block1
21408        block1"
21409            .unindent(),
21410        cx,
21411    )
21412    .await;
21413
21414    assert_indent_guides(
21415        1..10,
21416        vec![
21417            indent_guide(buffer_id, 1, 4, 0),
21418            indent_guide(buffer_id, 2, 3, 1),
21419            indent_guide(buffer_id, 3, 3, 2),
21420        ],
21421        None,
21422        &mut cx,
21423    );
21424}
21425
21426#[gpui::test]
21427async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21428    let (buffer_id, mut cx) = setup_indent_guides_editor(
21429        &"
21430        block1
21431            block2
21432                block3
21433
21434        block1
21435        block1"
21436            .unindent(),
21437        cx,
21438    )
21439    .await;
21440
21441    assert_indent_guides(
21442        0..6,
21443        vec![
21444            indent_guide(buffer_id, 1, 2, 0),
21445            indent_guide(buffer_id, 2, 2, 1),
21446        ],
21447        None,
21448        &mut cx,
21449    );
21450}
21451
21452#[gpui::test]
21453async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21454    let (buffer_id, mut cx) = setup_indent_guides_editor(
21455        &"
21456        function component() {
21457        \treturn (
21458        \t\t\t
21459        \t\t<div>
21460        \t\t\t<abc></abc>
21461        \t\t</div>
21462        \t)
21463        }"
21464        .unindent(),
21465        cx,
21466    )
21467    .await;
21468
21469    assert_indent_guides(
21470        0..8,
21471        vec![
21472            indent_guide(buffer_id, 1, 6, 0),
21473            indent_guide(buffer_id, 2, 5, 1),
21474            indent_guide(buffer_id, 4, 4, 2),
21475        ],
21476        None,
21477        &mut cx,
21478    );
21479}
21480
21481#[gpui::test]
21482async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21483    let (buffer_id, mut cx) = setup_indent_guides_editor(
21484        &"
21485        function component() {
21486        \treturn (
21487        \t
21488        \t\t<div>
21489        \t\t\t<abc></abc>
21490        \t\t</div>
21491        \t)
21492        }"
21493        .unindent(),
21494        cx,
21495    )
21496    .await;
21497
21498    assert_indent_guides(
21499        0..8,
21500        vec![
21501            indent_guide(buffer_id, 1, 6, 0),
21502            indent_guide(buffer_id, 2, 5, 1),
21503            indent_guide(buffer_id, 4, 4, 2),
21504        ],
21505        None,
21506        &mut cx,
21507    );
21508}
21509
21510#[gpui::test]
21511async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21512    let (buffer_id, mut cx) = setup_indent_guides_editor(
21513        &"
21514        block1
21515
21516
21517
21518            block2
21519        "
21520        .unindent(),
21521        cx,
21522    )
21523    .await;
21524
21525    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21526}
21527
21528#[gpui::test]
21529async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21530    let (buffer_id, mut cx) = setup_indent_guides_editor(
21531        &"
21532        def a:
21533        \tb = 3
21534        \tif True:
21535        \t\tc = 4
21536        \t\td = 5
21537        \tprint(b)
21538        "
21539        .unindent(),
21540        cx,
21541    )
21542    .await;
21543
21544    assert_indent_guides(
21545        0..6,
21546        vec![
21547            indent_guide(buffer_id, 1, 5, 0),
21548            indent_guide(buffer_id, 3, 4, 1),
21549        ],
21550        None,
21551        &mut cx,
21552    );
21553}
21554
21555#[gpui::test]
21556async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21557    let (buffer_id, mut cx) = setup_indent_guides_editor(
21558        &"
21559    fn main() {
21560        let a = 1;
21561    }"
21562        .unindent(),
21563        cx,
21564    )
21565    .await;
21566
21567    cx.update_editor(|editor, window, cx| {
21568        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21569            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21570        });
21571    });
21572
21573    assert_indent_guides(
21574        0..3,
21575        vec![indent_guide(buffer_id, 1, 1, 0)],
21576        Some(vec![0]),
21577        &mut cx,
21578    );
21579}
21580
21581#[gpui::test]
21582async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21583    let (buffer_id, mut cx) = setup_indent_guides_editor(
21584        &"
21585    fn main() {
21586        if 1 == 2 {
21587            let a = 1;
21588        }
21589    }"
21590        .unindent(),
21591        cx,
21592    )
21593    .await;
21594
21595    cx.update_editor(|editor, window, cx| {
21596        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21597            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21598        });
21599    });
21600
21601    assert_indent_guides(
21602        0..4,
21603        vec![
21604            indent_guide(buffer_id, 1, 3, 0),
21605            indent_guide(buffer_id, 2, 2, 1),
21606        ],
21607        Some(vec![1]),
21608        &mut cx,
21609    );
21610
21611    cx.update_editor(|editor, window, cx| {
21612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21613            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21614        });
21615    });
21616
21617    assert_indent_guides(
21618        0..4,
21619        vec![
21620            indent_guide(buffer_id, 1, 3, 0),
21621            indent_guide(buffer_id, 2, 2, 1),
21622        ],
21623        Some(vec![1]),
21624        &mut cx,
21625    );
21626
21627    cx.update_editor(|editor, window, cx| {
21628        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21629            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21630        });
21631    });
21632
21633    assert_indent_guides(
21634        0..4,
21635        vec![
21636            indent_guide(buffer_id, 1, 3, 0),
21637            indent_guide(buffer_id, 2, 2, 1),
21638        ],
21639        Some(vec![0]),
21640        &mut cx,
21641    );
21642}
21643
21644#[gpui::test]
21645async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21646    let (buffer_id, mut cx) = setup_indent_guides_editor(
21647        &"
21648    fn main() {
21649        let a = 1;
21650
21651        let b = 2;
21652    }"
21653        .unindent(),
21654        cx,
21655    )
21656    .await;
21657
21658    cx.update_editor(|editor, window, cx| {
21659        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21660            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21661        });
21662    });
21663
21664    assert_indent_guides(
21665        0..5,
21666        vec![indent_guide(buffer_id, 1, 3, 0)],
21667        Some(vec![0]),
21668        &mut cx,
21669    );
21670}
21671
21672#[gpui::test]
21673async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21674    let (buffer_id, mut cx) = setup_indent_guides_editor(
21675        &"
21676    def m:
21677        a = 1
21678        pass"
21679            .unindent(),
21680        cx,
21681    )
21682    .await;
21683
21684    cx.update_editor(|editor, window, cx| {
21685        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21686            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21687        });
21688    });
21689
21690    assert_indent_guides(
21691        0..3,
21692        vec![indent_guide(buffer_id, 1, 2, 0)],
21693        Some(vec![0]),
21694        &mut cx,
21695    );
21696}
21697
21698#[gpui::test]
21699async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21700    init_test(cx, |_| {});
21701    let mut cx = EditorTestContext::new(cx).await;
21702    let text = indoc! {
21703        "
21704        impl A {
21705            fn b() {
21706                0;
21707                3;
21708                5;
21709                6;
21710                7;
21711            }
21712        }
21713        "
21714    };
21715    let base_text = indoc! {
21716        "
21717        impl A {
21718            fn b() {
21719                0;
21720                1;
21721                2;
21722                3;
21723                4;
21724            }
21725            fn c() {
21726                5;
21727                6;
21728                7;
21729            }
21730        }
21731        "
21732    };
21733
21734    cx.update_editor(|editor, window, cx| {
21735        editor.set_text(text, window, cx);
21736
21737        editor.buffer().update(cx, |multibuffer, cx| {
21738            let buffer = multibuffer.as_singleton().unwrap();
21739            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21740
21741            multibuffer.set_all_diff_hunks_expanded(cx);
21742            multibuffer.add_diff(diff, cx);
21743
21744            buffer.read(cx).remote_id()
21745        })
21746    });
21747    cx.run_until_parked();
21748
21749    cx.assert_state_with_diff(
21750        indoc! { "
21751          impl A {
21752              fn b() {
21753                  0;
21754        -         1;
21755        -         2;
21756                  3;
21757        -         4;
21758        -     }
21759        -     fn c() {
21760                  5;
21761                  6;
21762                  7;
21763              }
21764          }
21765          ˇ"
21766        }
21767        .to_string(),
21768    );
21769
21770    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21771        editor
21772            .snapshot(window, cx)
21773            .buffer_snapshot()
21774            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21775            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21776            .collect::<Vec<_>>()
21777    });
21778    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21779    assert_eq!(
21780        actual_guides,
21781        vec![
21782            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21783            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21784            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21785        ]
21786    );
21787}
21788
21789#[gpui::test]
21790async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21791    init_test(cx, |_| {});
21792    let mut cx = EditorTestContext::new(cx).await;
21793
21794    let diff_base = r#"
21795        a
21796        b
21797        c
21798        "#
21799    .unindent();
21800
21801    cx.set_state(
21802        &r#"
21803        ˇA
21804        b
21805        C
21806        "#
21807        .unindent(),
21808    );
21809    cx.set_head_text(&diff_base);
21810    cx.update_editor(|editor, window, cx| {
21811        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21812    });
21813    executor.run_until_parked();
21814
21815    let both_hunks_expanded = r#"
21816        - a
21817        + ˇA
21818          b
21819        - c
21820        + C
21821        "#
21822    .unindent();
21823
21824    cx.assert_state_with_diff(both_hunks_expanded.clone());
21825
21826    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21827        let snapshot = editor.snapshot(window, cx);
21828        let hunks = editor
21829            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21830            .collect::<Vec<_>>();
21831        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21832        hunks
21833            .into_iter()
21834            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21835            .collect::<Vec<_>>()
21836    });
21837    assert_eq!(hunk_ranges.len(), 2);
21838
21839    cx.update_editor(|editor, _, cx| {
21840        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21841    });
21842    executor.run_until_parked();
21843
21844    let second_hunk_expanded = r#"
21845          ˇA
21846          b
21847        - c
21848        + C
21849        "#
21850    .unindent();
21851
21852    cx.assert_state_with_diff(second_hunk_expanded);
21853
21854    cx.update_editor(|editor, _, cx| {
21855        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21856    });
21857    executor.run_until_parked();
21858
21859    cx.assert_state_with_diff(both_hunks_expanded.clone());
21860
21861    cx.update_editor(|editor, _, cx| {
21862        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21863    });
21864    executor.run_until_parked();
21865
21866    let first_hunk_expanded = r#"
21867        - a
21868        + ˇA
21869          b
21870          C
21871        "#
21872    .unindent();
21873
21874    cx.assert_state_with_diff(first_hunk_expanded);
21875
21876    cx.update_editor(|editor, _, cx| {
21877        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21878    });
21879    executor.run_until_parked();
21880
21881    cx.assert_state_with_diff(both_hunks_expanded);
21882
21883    cx.set_state(
21884        &r#"
21885        ˇA
21886        b
21887        "#
21888        .unindent(),
21889    );
21890    cx.run_until_parked();
21891
21892    // TODO this cursor position seems bad
21893    cx.assert_state_with_diff(
21894        r#"
21895        - ˇa
21896        + A
21897          b
21898        "#
21899        .unindent(),
21900    );
21901
21902    cx.update_editor(|editor, window, cx| {
21903        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21904    });
21905
21906    cx.assert_state_with_diff(
21907        r#"
21908            - ˇa
21909            + A
21910              b
21911            - c
21912            "#
21913        .unindent(),
21914    );
21915
21916    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21917        let snapshot = editor.snapshot(window, cx);
21918        let hunks = editor
21919            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21920            .collect::<Vec<_>>();
21921        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21922        hunks
21923            .into_iter()
21924            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21925            .collect::<Vec<_>>()
21926    });
21927    assert_eq!(hunk_ranges.len(), 2);
21928
21929    cx.update_editor(|editor, _, cx| {
21930        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21931    });
21932    executor.run_until_parked();
21933
21934    cx.assert_state_with_diff(
21935        r#"
21936        - ˇa
21937        + A
21938          b
21939        "#
21940        .unindent(),
21941    );
21942}
21943
21944#[gpui::test]
21945async fn test_toggle_deletion_hunk_at_start_of_file(
21946    executor: BackgroundExecutor,
21947    cx: &mut TestAppContext,
21948) {
21949    init_test(cx, |_| {});
21950    let mut cx = EditorTestContext::new(cx).await;
21951
21952    let diff_base = r#"
21953        a
21954        b
21955        c
21956        "#
21957    .unindent();
21958
21959    cx.set_state(
21960        &r#"
21961        ˇb
21962        c
21963        "#
21964        .unindent(),
21965    );
21966    cx.set_head_text(&diff_base);
21967    cx.update_editor(|editor, window, cx| {
21968        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21969    });
21970    executor.run_until_parked();
21971
21972    let hunk_expanded = r#"
21973        - a
21974          ˇb
21975          c
21976        "#
21977    .unindent();
21978
21979    cx.assert_state_with_diff(hunk_expanded.clone());
21980
21981    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21982        let snapshot = editor.snapshot(window, cx);
21983        let hunks = editor
21984            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21985            .collect::<Vec<_>>();
21986        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21987        hunks
21988            .into_iter()
21989            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21990            .collect::<Vec<_>>()
21991    });
21992    assert_eq!(hunk_ranges.len(), 1);
21993
21994    cx.update_editor(|editor, _, cx| {
21995        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21996    });
21997    executor.run_until_parked();
21998
21999    let hunk_collapsed = r#"
22000          ˇb
22001          c
22002        "#
22003    .unindent();
22004
22005    cx.assert_state_with_diff(hunk_collapsed);
22006
22007    cx.update_editor(|editor, _, cx| {
22008        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
22009    });
22010    executor.run_until_parked();
22011
22012    cx.assert_state_with_diff(hunk_expanded);
22013}
22014
22015#[gpui::test]
22016async fn test_display_diff_hunks(cx: &mut TestAppContext) {
22017    init_test(cx, |_| {});
22018
22019    let fs = FakeFs::new(cx.executor());
22020    fs.insert_tree(
22021        path!("/test"),
22022        json!({
22023            ".git": {},
22024            "file-1": "ONE\n",
22025            "file-2": "TWO\n",
22026            "file-3": "THREE\n",
22027        }),
22028    )
22029    .await;
22030
22031    fs.set_head_for_repo(
22032        path!("/test/.git").as_ref(),
22033        &[
22034            ("file-1", "one\n".into()),
22035            ("file-2", "two\n".into()),
22036            ("file-3", "three\n".into()),
22037        ],
22038        "deadbeef",
22039    );
22040
22041    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
22042    let mut buffers = vec![];
22043    for i in 1..=3 {
22044        let buffer = project
22045            .update(cx, |project, cx| {
22046                let path = format!(path!("/test/file-{}"), i);
22047                project.open_local_buffer(path, cx)
22048            })
22049            .await
22050            .unwrap();
22051        buffers.push(buffer);
22052    }
22053
22054    let multibuffer = cx.new(|cx| {
22055        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
22056        multibuffer.set_all_diff_hunks_expanded(cx);
22057        for buffer in &buffers {
22058            let snapshot = buffer.read(cx).snapshot();
22059            multibuffer.set_excerpts_for_path(
22060                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
22061                buffer.clone(),
22062                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
22063                2,
22064                cx,
22065            );
22066        }
22067        multibuffer
22068    });
22069
22070    let editor = cx.add_window(|window, cx| {
22071        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
22072    });
22073    cx.run_until_parked();
22074
22075    let snapshot = editor
22076        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22077        .unwrap();
22078    let hunks = snapshot
22079        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
22080        .map(|hunk| match hunk {
22081            DisplayDiffHunk::Unfolded {
22082                display_row_range, ..
22083            } => display_row_range,
22084            DisplayDiffHunk::Folded { .. } => unreachable!(),
22085        })
22086        .collect::<Vec<_>>();
22087    assert_eq!(
22088        hunks,
22089        [
22090            DisplayRow(2)..DisplayRow(4),
22091            DisplayRow(7)..DisplayRow(9),
22092            DisplayRow(12)..DisplayRow(14),
22093        ]
22094    );
22095}
22096
22097#[gpui::test]
22098async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
22099    init_test(cx, |_| {});
22100
22101    let mut cx = EditorTestContext::new(cx).await;
22102    cx.set_head_text(indoc! { "
22103        one
22104        two
22105        three
22106        four
22107        five
22108        "
22109    });
22110    cx.set_index_text(indoc! { "
22111        one
22112        two
22113        three
22114        four
22115        five
22116        "
22117    });
22118    cx.set_state(indoc! {"
22119        one
22120        TWO
22121        ˇTHREE
22122        FOUR
22123        five
22124    "});
22125    cx.run_until_parked();
22126    cx.update_editor(|editor, window, cx| {
22127        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22128    });
22129    cx.run_until_parked();
22130    cx.assert_index_text(Some(indoc! {"
22131        one
22132        TWO
22133        THREE
22134        FOUR
22135        five
22136    "}));
22137    cx.set_state(indoc! { "
22138        one
22139        TWO
22140        ˇTHREE-HUNDRED
22141        FOUR
22142        five
22143    "});
22144    cx.run_until_parked();
22145    cx.update_editor(|editor, window, cx| {
22146        let snapshot = editor.snapshot(window, cx);
22147        let hunks = editor
22148            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
22149            .collect::<Vec<_>>();
22150        assert_eq!(hunks.len(), 1);
22151        assert_eq!(
22152            hunks[0].status(),
22153            DiffHunkStatus {
22154                kind: DiffHunkStatusKind::Modified,
22155                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
22156            }
22157        );
22158
22159        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
22160    });
22161    cx.run_until_parked();
22162    cx.assert_index_text(Some(indoc! {"
22163        one
22164        TWO
22165        THREE-HUNDRED
22166        FOUR
22167        five
22168    "}));
22169}
22170
22171#[gpui::test]
22172fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
22173    init_test(cx, |_| {});
22174
22175    let editor = cx.add_window(|window, cx| {
22176        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
22177        build_editor(buffer, window, cx)
22178    });
22179
22180    let render_args = Arc::new(Mutex::new(None));
22181    let snapshot = editor
22182        .update(cx, |editor, window, cx| {
22183            let snapshot = editor.buffer().read(cx).snapshot(cx);
22184            let range =
22185                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
22186
22187            struct RenderArgs {
22188                row: MultiBufferRow,
22189                folded: bool,
22190                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
22191            }
22192
22193            let crease = Crease::inline(
22194                range,
22195                FoldPlaceholder::test(),
22196                {
22197                    let toggle_callback = render_args.clone();
22198                    move |row, folded, callback, _window, _cx| {
22199                        *toggle_callback.lock() = Some(RenderArgs {
22200                            row,
22201                            folded,
22202                            callback,
22203                        });
22204                        div()
22205                    }
22206                },
22207                |_row, _folded, _window, _cx| div(),
22208            );
22209
22210            editor.insert_creases(Some(crease), cx);
22211            let snapshot = editor.snapshot(window, cx);
22212            let _div =
22213                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
22214            snapshot
22215        })
22216        .unwrap();
22217
22218    let render_args = render_args.lock().take().unwrap();
22219    assert_eq!(render_args.row, MultiBufferRow(1));
22220    assert!(!render_args.folded);
22221    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22222
22223    cx.update_window(*editor, |_, window, cx| {
22224        (render_args.callback)(true, window, cx)
22225    })
22226    .unwrap();
22227    let snapshot = editor
22228        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22229        .unwrap();
22230    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
22231
22232    cx.update_window(*editor, |_, window, cx| {
22233        (render_args.callback)(false, window, cx)
22234    })
22235    .unwrap();
22236    let snapshot = editor
22237        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
22238        .unwrap();
22239    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
22240}
22241
22242#[gpui::test]
22243async fn test_input_text(cx: &mut TestAppContext) {
22244    init_test(cx, |_| {});
22245    let mut cx = EditorTestContext::new(cx).await;
22246
22247    cx.set_state(
22248        &r#"ˇone
22249        two
22250
22251        three
22252        fourˇ
22253        five
22254
22255        siˇx"#
22256            .unindent(),
22257    );
22258
22259    cx.dispatch_action(HandleInput(String::new()));
22260    cx.assert_editor_state(
22261        &r#"ˇone
22262        two
22263
22264        three
22265        fourˇ
22266        five
22267
22268        siˇx"#
22269            .unindent(),
22270    );
22271
22272    cx.dispatch_action(HandleInput("AAAA".to_string()));
22273    cx.assert_editor_state(
22274        &r#"AAAAˇone
22275        two
22276
22277        three
22278        fourAAAAˇ
22279        five
22280
22281        siAAAAˇx"#
22282            .unindent(),
22283    );
22284}
22285
22286#[gpui::test]
22287async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22288    init_test(cx, |_| {});
22289
22290    let mut cx = EditorTestContext::new(cx).await;
22291    cx.set_state(
22292        r#"let foo = 1;
22293let foo = 2;
22294let foo = 3;
22295let fooˇ = 4;
22296let foo = 5;
22297let foo = 6;
22298let foo = 7;
22299let foo = 8;
22300let foo = 9;
22301let foo = 10;
22302let foo = 11;
22303let foo = 12;
22304let foo = 13;
22305let foo = 14;
22306let foo = 15;"#,
22307    );
22308
22309    cx.update_editor(|e, window, cx| {
22310        assert_eq!(
22311            e.next_scroll_position,
22312            NextScrollCursorCenterTopBottom::Center,
22313            "Default next scroll direction is center",
22314        );
22315
22316        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22317        assert_eq!(
22318            e.next_scroll_position,
22319            NextScrollCursorCenterTopBottom::Top,
22320            "After center, next scroll direction should be top",
22321        );
22322
22323        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22324        assert_eq!(
22325            e.next_scroll_position,
22326            NextScrollCursorCenterTopBottom::Bottom,
22327            "After top, next scroll direction should be bottom",
22328        );
22329
22330        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22331        assert_eq!(
22332            e.next_scroll_position,
22333            NextScrollCursorCenterTopBottom::Center,
22334            "After bottom, scrolling should start over",
22335        );
22336
22337        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22338        assert_eq!(
22339            e.next_scroll_position,
22340            NextScrollCursorCenterTopBottom::Top,
22341            "Scrolling continues if retriggered fast enough"
22342        );
22343    });
22344
22345    cx.executor()
22346        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22347    cx.executor().run_until_parked();
22348    cx.update_editor(|e, _, _| {
22349        assert_eq!(
22350            e.next_scroll_position,
22351            NextScrollCursorCenterTopBottom::Center,
22352            "If scrolling is not triggered fast enough, it should reset"
22353        );
22354    });
22355}
22356
22357#[gpui::test]
22358async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22359    init_test(cx, |_| {});
22360    let mut cx = EditorLspTestContext::new_rust(
22361        lsp::ServerCapabilities {
22362            definition_provider: Some(lsp::OneOf::Left(true)),
22363            references_provider: Some(lsp::OneOf::Left(true)),
22364            ..lsp::ServerCapabilities::default()
22365        },
22366        cx,
22367    )
22368    .await;
22369
22370    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22371        let go_to_definition = cx
22372            .lsp
22373            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22374                move |params, _| async move {
22375                    if empty_go_to_definition {
22376                        Ok(None)
22377                    } else {
22378                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22379                            uri: params.text_document_position_params.text_document.uri,
22380                            range: lsp::Range::new(
22381                                lsp::Position::new(4, 3),
22382                                lsp::Position::new(4, 6),
22383                            ),
22384                        })))
22385                    }
22386                },
22387            );
22388        let references = cx
22389            .lsp
22390            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22391                Ok(Some(vec![lsp::Location {
22392                    uri: params.text_document_position.text_document.uri,
22393                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22394                }]))
22395            });
22396        (go_to_definition, references)
22397    };
22398
22399    cx.set_state(
22400        &r#"fn one() {
22401            let mut a = ˇtwo();
22402        }
22403
22404        fn two() {}"#
22405            .unindent(),
22406    );
22407    set_up_lsp_handlers(false, &mut cx);
22408    let navigated = cx
22409        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22410        .await
22411        .expect("Failed to navigate to definition");
22412    assert_eq!(
22413        navigated,
22414        Navigated::Yes,
22415        "Should have navigated to definition from the GetDefinition response"
22416    );
22417    cx.assert_editor_state(
22418        &r#"fn one() {
22419            let mut a = two();
22420        }
22421
22422        fn «twoˇ»() {}"#
22423            .unindent(),
22424    );
22425
22426    let editors = cx.update_workspace(|workspace, _, cx| {
22427        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22428    });
22429    cx.update_editor(|_, _, test_editor_cx| {
22430        assert_eq!(
22431            editors.len(),
22432            1,
22433            "Initially, only one, test, editor should be open in the workspace"
22434        );
22435        assert_eq!(
22436            test_editor_cx.entity(),
22437            editors.last().expect("Asserted len is 1").clone()
22438        );
22439    });
22440
22441    set_up_lsp_handlers(true, &mut cx);
22442    let navigated = cx
22443        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22444        .await
22445        .expect("Failed to navigate to lookup references");
22446    assert_eq!(
22447        navigated,
22448        Navigated::Yes,
22449        "Should have navigated to references as a fallback after empty GoToDefinition response"
22450    );
22451    // We should not change the selections in the existing file,
22452    // if opening another milti buffer with the references
22453    cx.assert_editor_state(
22454        &r#"fn one() {
22455            let mut a = two();
22456        }
22457
22458        fn «twoˇ»() {}"#
22459            .unindent(),
22460    );
22461    let editors = cx.update_workspace(|workspace, _, cx| {
22462        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22463    });
22464    cx.update_editor(|_, _, test_editor_cx| {
22465        assert_eq!(
22466            editors.len(),
22467            2,
22468            "After falling back to references search, we open a new editor with the results"
22469        );
22470        let references_fallback_text = editors
22471            .into_iter()
22472            .find(|new_editor| *new_editor != test_editor_cx.entity())
22473            .expect("Should have one non-test editor now")
22474            .read(test_editor_cx)
22475            .text(test_editor_cx);
22476        assert_eq!(
22477            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22478            "Should use the range from the references response and not the GoToDefinition one"
22479        );
22480    });
22481}
22482
22483#[gpui::test]
22484async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22485    init_test(cx, |_| {});
22486    cx.update(|cx| {
22487        let mut editor_settings = EditorSettings::get_global(cx).clone();
22488        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22489        EditorSettings::override_global(editor_settings, cx);
22490    });
22491    let mut cx = EditorLspTestContext::new_rust(
22492        lsp::ServerCapabilities {
22493            definition_provider: Some(lsp::OneOf::Left(true)),
22494            references_provider: Some(lsp::OneOf::Left(true)),
22495            ..lsp::ServerCapabilities::default()
22496        },
22497        cx,
22498    )
22499    .await;
22500    let original_state = r#"fn one() {
22501        let mut a = ˇtwo();
22502    }
22503
22504    fn two() {}"#
22505        .unindent();
22506    cx.set_state(&original_state);
22507
22508    let mut go_to_definition = cx
22509        .lsp
22510        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22511            move |_, _| async move { Ok(None) },
22512        );
22513    let _references = cx
22514        .lsp
22515        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22516            panic!("Should not call for references with no go to definition fallback")
22517        });
22518
22519    let navigated = cx
22520        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22521        .await
22522        .expect("Failed to navigate to lookup references");
22523    go_to_definition
22524        .next()
22525        .await
22526        .expect("Should have called the go_to_definition handler");
22527
22528    assert_eq!(
22529        navigated,
22530        Navigated::No,
22531        "Should have navigated to references as a fallback after empty GoToDefinition response"
22532    );
22533    cx.assert_editor_state(&original_state);
22534    let editors = cx.update_workspace(|workspace, _, cx| {
22535        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22536    });
22537    cx.update_editor(|_, _, _| {
22538        assert_eq!(
22539            editors.len(),
22540            1,
22541            "After unsuccessful fallback, no other editor should have been opened"
22542        );
22543    });
22544}
22545
22546#[gpui::test]
22547async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22548    init_test(cx, |_| {});
22549    let mut cx = EditorLspTestContext::new_rust(
22550        lsp::ServerCapabilities {
22551            references_provider: Some(lsp::OneOf::Left(true)),
22552            ..lsp::ServerCapabilities::default()
22553        },
22554        cx,
22555    )
22556    .await;
22557
22558    cx.set_state(
22559        &r#"
22560        fn one() {
22561            let mut a = two();
22562        }
22563
22564        fn ˇtwo() {}"#
22565            .unindent(),
22566    );
22567    cx.lsp
22568        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22569            Ok(Some(vec![
22570                lsp::Location {
22571                    uri: params.text_document_position.text_document.uri.clone(),
22572                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22573                },
22574                lsp::Location {
22575                    uri: params.text_document_position.text_document.uri,
22576                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22577                },
22578            ]))
22579        });
22580    let navigated = cx
22581        .update_editor(|editor, window, cx| {
22582            editor.find_all_references(&FindAllReferences::default(), window, cx)
22583        })
22584        .unwrap()
22585        .await
22586        .expect("Failed to navigate to references");
22587    assert_eq!(
22588        navigated,
22589        Navigated::Yes,
22590        "Should have navigated to references from the FindAllReferences response"
22591    );
22592    cx.assert_editor_state(
22593        &r#"fn one() {
22594            let mut a = two();
22595        }
22596
22597        fn ˇtwo() {}"#
22598            .unindent(),
22599    );
22600
22601    let editors = cx.update_workspace(|workspace, _, cx| {
22602        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22603    });
22604    cx.update_editor(|_, _, _| {
22605        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22606    });
22607
22608    cx.set_state(
22609        &r#"fn one() {
22610            let mut a = ˇtwo();
22611        }
22612
22613        fn two() {}"#
22614            .unindent(),
22615    );
22616    let navigated = cx
22617        .update_editor(|editor, window, cx| {
22618            editor.find_all_references(&FindAllReferences::default(), window, cx)
22619        })
22620        .unwrap()
22621        .await
22622        .expect("Failed to navigate to references");
22623    assert_eq!(
22624        navigated,
22625        Navigated::Yes,
22626        "Should have navigated to references from the FindAllReferences response"
22627    );
22628    cx.assert_editor_state(
22629        &r#"fn one() {
22630            let mut a = ˇtwo();
22631        }
22632
22633        fn two() {}"#
22634            .unindent(),
22635    );
22636    let editors = cx.update_workspace(|workspace, _, cx| {
22637        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22638    });
22639    cx.update_editor(|_, _, _| {
22640        assert_eq!(
22641            editors.len(),
22642            2,
22643            "should have re-used the previous multibuffer"
22644        );
22645    });
22646
22647    cx.set_state(
22648        &r#"fn one() {
22649            let mut a = ˇtwo();
22650        }
22651        fn three() {}
22652        fn two() {}"#
22653            .unindent(),
22654    );
22655    cx.lsp
22656        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22657            Ok(Some(vec![
22658                lsp::Location {
22659                    uri: params.text_document_position.text_document.uri.clone(),
22660                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22661                },
22662                lsp::Location {
22663                    uri: params.text_document_position.text_document.uri,
22664                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22665                },
22666            ]))
22667        });
22668    let navigated = cx
22669        .update_editor(|editor, window, cx| {
22670            editor.find_all_references(&FindAllReferences::default(), window, cx)
22671        })
22672        .unwrap()
22673        .await
22674        .expect("Failed to navigate to references");
22675    assert_eq!(
22676        navigated,
22677        Navigated::Yes,
22678        "Should have navigated to references from the FindAllReferences response"
22679    );
22680    cx.assert_editor_state(
22681        &r#"fn one() {
22682                let mut a = ˇtwo();
22683            }
22684            fn three() {}
22685            fn two() {}"#
22686            .unindent(),
22687    );
22688    let editors = cx.update_workspace(|workspace, _, cx| {
22689        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22690    });
22691    cx.update_editor(|_, _, _| {
22692        assert_eq!(
22693            editors.len(),
22694            3,
22695            "should have used a new multibuffer as offsets changed"
22696        );
22697    });
22698}
22699#[gpui::test]
22700async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22701    init_test(cx, |_| {});
22702
22703    let language = Arc::new(Language::new(
22704        LanguageConfig::default(),
22705        Some(tree_sitter_rust::LANGUAGE.into()),
22706    ));
22707
22708    let text = r#"
22709        #[cfg(test)]
22710        mod tests() {
22711            #[test]
22712            fn runnable_1() {
22713                let a = 1;
22714            }
22715
22716            #[test]
22717            fn runnable_2() {
22718                let a = 1;
22719                let b = 2;
22720            }
22721        }
22722    "#
22723    .unindent();
22724
22725    let fs = FakeFs::new(cx.executor());
22726    fs.insert_file("/file.rs", Default::default()).await;
22727
22728    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22729    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22730    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22731    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
22732    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22733
22734    let editor = cx.new_window_entity(|window, cx| {
22735        Editor::new(
22736            EditorMode::full(),
22737            multi_buffer,
22738            Some(project.clone()),
22739            window,
22740            cx,
22741        )
22742    });
22743
22744    editor.update_in(cx, |editor, window, cx| {
22745        let snapshot = editor.buffer().read(cx).snapshot(cx);
22746        editor.tasks.insert(
22747            (buffer.read(cx).remote_id(), 3),
22748            RunnableTasks {
22749                templates: vec![],
22750                offset: snapshot.anchor_before(MultiBufferOffset(43)),
22751                column: 0,
22752                extra_variables: HashMap::default(),
22753                context_range: BufferOffset(43)..BufferOffset(85),
22754            },
22755        );
22756        editor.tasks.insert(
22757            (buffer.read(cx).remote_id(), 8),
22758            RunnableTasks {
22759                templates: vec![],
22760                offset: snapshot.anchor_before(MultiBufferOffset(86)),
22761                column: 0,
22762                extra_variables: HashMap::default(),
22763                context_range: BufferOffset(86)..BufferOffset(191),
22764            },
22765        );
22766
22767        // Test finding task when cursor is inside function body
22768        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22769            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22770        });
22771        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22772        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22773
22774        // Test finding task when cursor is on function name
22775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22776            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22777        });
22778        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22779        assert_eq!(row, 8, "Should find task when cursor is on function name");
22780    });
22781}
22782
22783#[gpui::test]
22784async fn test_folding_buffers(cx: &mut TestAppContext) {
22785    init_test(cx, |_| {});
22786
22787    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22788    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22789    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22790
22791    let fs = FakeFs::new(cx.executor());
22792    fs.insert_tree(
22793        path!("/a"),
22794        json!({
22795            "first.rs": sample_text_1,
22796            "second.rs": sample_text_2,
22797            "third.rs": sample_text_3,
22798        }),
22799    )
22800    .await;
22801    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22802    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22803    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22804    let worktree = project.update(cx, |project, cx| {
22805        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22806        assert_eq!(worktrees.len(), 1);
22807        worktrees.pop().unwrap()
22808    });
22809    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22810
22811    let buffer_1 = project
22812        .update(cx, |project, cx| {
22813            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22814        })
22815        .await
22816        .unwrap();
22817    let buffer_2 = project
22818        .update(cx, |project, cx| {
22819            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22820        })
22821        .await
22822        .unwrap();
22823    let buffer_3 = project
22824        .update(cx, |project, cx| {
22825            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22826        })
22827        .await
22828        .unwrap();
22829
22830    let multi_buffer = cx.new(|cx| {
22831        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22832        multi_buffer.push_excerpts(
22833            buffer_1.clone(),
22834            [
22835                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22836                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22837                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22838            ],
22839            cx,
22840        );
22841        multi_buffer.push_excerpts(
22842            buffer_2.clone(),
22843            [
22844                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22845                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22846                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22847            ],
22848            cx,
22849        );
22850        multi_buffer.push_excerpts(
22851            buffer_3.clone(),
22852            [
22853                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22854                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22855                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22856            ],
22857            cx,
22858        );
22859        multi_buffer
22860    });
22861    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22862        Editor::new(
22863            EditorMode::full(),
22864            multi_buffer.clone(),
22865            Some(project.clone()),
22866            window,
22867            cx,
22868        )
22869    });
22870
22871    assert_eq!(
22872        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22873        "\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",
22874    );
22875
22876    multi_buffer_editor.update(cx, |editor, cx| {
22877        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22878    });
22879    assert_eq!(
22880        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22881        "\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",
22882        "After folding the first buffer, its text should not be displayed"
22883    );
22884
22885    multi_buffer_editor.update(cx, |editor, cx| {
22886        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22887    });
22888    assert_eq!(
22889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22890        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22891        "After folding the second buffer, its text should not be displayed"
22892    );
22893
22894    multi_buffer_editor.update(cx, |editor, cx| {
22895        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22896    });
22897    assert_eq!(
22898        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22899        "\n\n\n\n\n",
22900        "After folding the third buffer, its text should not be displayed"
22901    );
22902
22903    // Emulate selection inside the fold logic, that should work
22904    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22905        editor
22906            .snapshot(window, cx)
22907            .next_line_boundary(Point::new(0, 4));
22908    });
22909
22910    multi_buffer_editor.update(cx, |editor, cx| {
22911        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22912    });
22913    assert_eq!(
22914        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22915        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22916        "After unfolding the second buffer, its text should be displayed"
22917    );
22918
22919    // Typing inside of buffer 1 causes that buffer to be unfolded.
22920    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22921        assert_eq!(
22922            multi_buffer
22923                .read(cx)
22924                .snapshot(cx)
22925                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22926                .collect::<String>(),
22927            "bbbb"
22928        );
22929        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22930            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22931        });
22932        editor.handle_input("B", window, cx);
22933    });
22934
22935    assert_eq!(
22936        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22937        "\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",
22938        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22939    );
22940
22941    multi_buffer_editor.update(cx, |editor, cx| {
22942        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22943    });
22944    assert_eq!(
22945        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22946        "\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",
22947        "After unfolding the all buffers, all original text should be displayed"
22948    );
22949}
22950
22951#[gpui::test]
22952async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22953    init_test(cx, |_| {});
22954
22955    let sample_text_1 = "1111\n2222\n3333".to_string();
22956    let sample_text_2 = "4444\n5555\n6666".to_string();
22957    let sample_text_3 = "7777\n8888\n9999".to_string();
22958
22959    let fs = FakeFs::new(cx.executor());
22960    fs.insert_tree(
22961        path!("/a"),
22962        json!({
22963            "first.rs": sample_text_1,
22964            "second.rs": sample_text_2,
22965            "third.rs": sample_text_3,
22966        }),
22967    )
22968    .await;
22969    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22970    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22971    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22972    let worktree = project.update(cx, |project, cx| {
22973        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22974        assert_eq!(worktrees.len(), 1);
22975        worktrees.pop().unwrap()
22976    });
22977    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22978
22979    let buffer_1 = project
22980        .update(cx, |project, cx| {
22981            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22982        })
22983        .await
22984        .unwrap();
22985    let buffer_2 = project
22986        .update(cx, |project, cx| {
22987            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22988        })
22989        .await
22990        .unwrap();
22991    let buffer_3 = project
22992        .update(cx, |project, cx| {
22993            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22994        })
22995        .await
22996        .unwrap();
22997
22998    let multi_buffer = cx.new(|cx| {
22999        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23000        multi_buffer.push_excerpts(
23001            buffer_1.clone(),
23002            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23003            cx,
23004        );
23005        multi_buffer.push_excerpts(
23006            buffer_2.clone(),
23007            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23008            cx,
23009        );
23010        multi_buffer.push_excerpts(
23011            buffer_3.clone(),
23012            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
23013            cx,
23014        );
23015        multi_buffer
23016    });
23017
23018    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23019        Editor::new(
23020            EditorMode::full(),
23021            multi_buffer,
23022            Some(project.clone()),
23023            window,
23024            cx,
23025        )
23026    });
23027
23028    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
23029    assert_eq!(
23030        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23031        full_text,
23032    );
23033
23034    multi_buffer_editor.update(cx, |editor, cx| {
23035        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
23036    });
23037    assert_eq!(
23038        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23039        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
23040        "After folding the first buffer, its text should not be displayed"
23041    );
23042
23043    multi_buffer_editor.update(cx, |editor, cx| {
23044        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
23045    });
23046
23047    assert_eq!(
23048        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23049        "\n\n\n\n\n\n7777\n8888\n9999",
23050        "After folding the second buffer, its text should not be displayed"
23051    );
23052
23053    multi_buffer_editor.update(cx, |editor, cx| {
23054        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
23055    });
23056    assert_eq!(
23057        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23058        "\n\n\n\n\n",
23059        "After folding the third buffer, its text should not be displayed"
23060    );
23061
23062    multi_buffer_editor.update(cx, |editor, cx| {
23063        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
23064    });
23065    assert_eq!(
23066        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23067        "\n\n\n\n4444\n5555\n6666\n\n",
23068        "After unfolding the second buffer, its text should be displayed"
23069    );
23070
23071    multi_buffer_editor.update(cx, |editor, cx| {
23072        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
23073    });
23074    assert_eq!(
23075        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23076        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
23077        "After unfolding the first buffer, its text should be displayed"
23078    );
23079
23080    multi_buffer_editor.update(cx, |editor, cx| {
23081        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
23082    });
23083    assert_eq!(
23084        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23085        full_text,
23086        "After unfolding all buffers, all original text should be displayed"
23087    );
23088}
23089
23090#[gpui::test]
23091async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
23092    init_test(cx, |_| {});
23093
23094    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
23095
23096    let fs = FakeFs::new(cx.executor());
23097    fs.insert_tree(
23098        path!("/a"),
23099        json!({
23100            "main.rs": sample_text,
23101        }),
23102    )
23103    .await;
23104    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23105    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23106    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23107    let worktree = project.update(cx, |project, cx| {
23108        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
23109        assert_eq!(worktrees.len(), 1);
23110        worktrees.pop().unwrap()
23111    });
23112    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
23113
23114    let buffer_1 = project
23115        .update(cx, |project, cx| {
23116            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23117        })
23118        .await
23119        .unwrap();
23120
23121    let multi_buffer = cx.new(|cx| {
23122        let mut multi_buffer = MultiBuffer::new(ReadWrite);
23123        multi_buffer.push_excerpts(
23124            buffer_1.clone(),
23125            [ExcerptRange::new(
23126                Point::new(0, 0)
23127                    ..Point::new(
23128                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
23129                        0,
23130                    ),
23131            )],
23132            cx,
23133        );
23134        multi_buffer
23135    });
23136    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
23137        Editor::new(
23138            EditorMode::full(),
23139            multi_buffer,
23140            Some(project.clone()),
23141            window,
23142            cx,
23143        )
23144    });
23145
23146    let selection_range = Point::new(1, 0)..Point::new(2, 0);
23147    multi_buffer_editor.update_in(cx, |editor, window, cx| {
23148        enum TestHighlight {}
23149        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
23150        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
23151        editor.highlight_text::<TestHighlight>(
23152            vec![highlight_range.clone()],
23153            HighlightStyle::color(Hsla::green()),
23154            cx,
23155        );
23156        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23157            s.select_ranges(Some(highlight_range))
23158        });
23159    });
23160
23161    let full_text = format!("\n\n{sample_text}");
23162    assert_eq!(
23163        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
23164        full_text,
23165    );
23166}
23167
23168#[gpui::test]
23169async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
23170    init_test(cx, |_| {});
23171    cx.update(|cx| {
23172        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
23173            "keymaps/default-linux.json",
23174            cx,
23175        )
23176        .unwrap();
23177        cx.bind_keys(default_key_bindings);
23178    });
23179
23180    let (editor, cx) = cx.add_window_view(|window, cx| {
23181        let multi_buffer = MultiBuffer::build_multi(
23182            [
23183                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
23184                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
23185                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
23186                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
23187            ],
23188            cx,
23189        );
23190        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
23191
23192        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
23193        // fold all but the second buffer, so that we test navigating between two
23194        // adjacent folded buffers, as well as folded buffers at the start and
23195        // end the multibuffer
23196        editor.fold_buffer(buffer_ids[0], cx);
23197        editor.fold_buffer(buffer_ids[2], cx);
23198        editor.fold_buffer(buffer_ids[3], cx);
23199
23200        editor
23201    });
23202    cx.simulate_resize(size(px(1000.), px(1000.)));
23203
23204    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
23205    cx.assert_excerpts_with_selections(indoc! {"
23206        [EXCERPT]
23207        ˇ[FOLDED]
23208        [EXCERPT]
23209        a1
23210        b1
23211        [EXCERPT]
23212        [FOLDED]
23213        [EXCERPT]
23214        [FOLDED]
23215        "
23216    });
23217    cx.simulate_keystroke("down");
23218    cx.assert_excerpts_with_selections(indoc! {"
23219        [EXCERPT]
23220        [FOLDED]
23221        [EXCERPT]
23222        ˇa1
23223        b1
23224        [EXCERPT]
23225        [FOLDED]
23226        [EXCERPT]
23227        [FOLDED]
23228        "
23229    });
23230    cx.simulate_keystroke("down");
23231    cx.assert_excerpts_with_selections(indoc! {"
23232        [EXCERPT]
23233        [FOLDED]
23234        [EXCERPT]
23235        a1
23236        ˇb1
23237        [EXCERPT]
23238        [FOLDED]
23239        [EXCERPT]
23240        [FOLDED]
23241        "
23242    });
23243    cx.simulate_keystroke("down");
23244    cx.assert_excerpts_with_selections(indoc! {"
23245        [EXCERPT]
23246        [FOLDED]
23247        [EXCERPT]
23248        a1
23249        b1
23250        ˇ[EXCERPT]
23251        [FOLDED]
23252        [EXCERPT]
23253        [FOLDED]
23254        "
23255    });
23256    cx.simulate_keystroke("down");
23257    cx.assert_excerpts_with_selections(indoc! {"
23258        [EXCERPT]
23259        [FOLDED]
23260        [EXCERPT]
23261        a1
23262        b1
23263        [EXCERPT]
23264        ˇ[FOLDED]
23265        [EXCERPT]
23266        [FOLDED]
23267        "
23268    });
23269    for _ in 0..5 {
23270        cx.simulate_keystroke("down");
23271        cx.assert_excerpts_with_selections(indoc! {"
23272            [EXCERPT]
23273            [FOLDED]
23274            [EXCERPT]
23275            a1
23276            b1
23277            [EXCERPT]
23278            [FOLDED]
23279            [EXCERPT]
23280            ˇ[FOLDED]
23281            "
23282        });
23283    }
23284
23285    cx.simulate_keystroke("up");
23286    cx.assert_excerpts_with_selections(indoc! {"
23287        [EXCERPT]
23288        [FOLDED]
23289        [EXCERPT]
23290        a1
23291        b1
23292        [EXCERPT]
23293        ˇ[FOLDED]
23294        [EXCERPT]
23295        [FOLDED]
23296        "
23297    });
23298    cx.simulate_keystroke("up");
23299    cx.assert_excerpts_with_selections(indoc! {"
23300        [EXCERPT]
23301        [FOLDED]
23302        [EXCERPT]
23303        a1
23304        b1
23305        ˇ[EXCERPT]
23306        [FOLDED]
23307        [EXCERPT]
23308        [FOLDED]
23309        "
23310    });
23311    cx.simulate_keystroke("up");
23312    cx.assert_excerpts_with_selections(indoc! {"
23313        [EXCERPT]
23314        [FOLDED]
23315        [EXCERPT]
23316        a1
23317        ˇb1
23318        [EXCERPT]
23319        [FOLDED]
23320        [EXCERPT]
23321        [FOLDED]
23322        "
23323    });
23324    cx.simulate_keystroke("up");
23325    cx.assert_excerpts_with_selections(indoc! {"
23326        [EXCERPT]
23327        [FOLDED]
23328        [EXCERPT]
23329        ˇa1
23330        b1
23331        [EXCERPT]
23332        [FOLDED]
23333        [EXCERPT]
23334        [FOLDED]
23335        "
23336    });
23337    for _ in 0..5 {
23338        cx.simulate_keystroke("up");
23339        cx.assert_excerpts_with_selections(indoc! {"
23340            [EXCERPT]
23341            ˇ[FOLDED]
23342            [EXCERPT]
23343            a1
23344            b1
23345            [EXCERPT]
23346            [FOLDED]
23347            [EXCERPT]
23348            [FOLDED]
23349            "
23350        });
23351    }
23352}
23353
23354#[gpui::test]
23355async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23356    init_test(cx, |_| {});
23357
23358    // Simple insertion
23359    assert_highlighted_edits(
23360        "Hello, world!",
23361        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23362        true,
23363        cx,
23364        |highlighted_edits, cx| {
23365            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23366            assert_eq!(highlighted_edits.highlights.len(), 1);
23367            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23368            assert_eq!(
23369                highlighted_edits.highlights[0].1.background_color,
23370                Some(cx.theme().status().created_background)
23371            );
23372        },
23373    )
23374    .await;
23375
23376    // Replacement
23377    assert_highlighted_edits(
23378        "This is a test.",
23379        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23380        false,
23381        cx,
23382        |highlighted_edits, cx| {
23383            assert_eq!(highlighted_edits.text, "That is a test.");
23384            assert_eq!(highlighted_edits.highlights.len(), 1);
23385            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23386            assert_eq!(
23387                highlighted_edits.highlights[0].1.background_color,
23388                Some(cx.theme().status().created_background)
23389            );
23390        },
23391    )
23392    .await;
23393
23394    // Multiple edits
23395    assert_highlighted_edits(
23396        "Hello, world!",
23397        vec![
23398            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23399            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23400        ],
23401        false,
23402        cx,
23403        |highlighted_edits, cx| {
23404            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23405            assert_eq!(highlighted_edits.highlights.len(), 2);
23406            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23407            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23408            assert_eq!(
23409                highlighted_edits.highlights[0].1.background_color,
23410                Some(cx.theme().status().created_background)
23411            );
23412            assert_eq!(
23413                highlighted_edits.highlights[1].1.background_color,
23414                Some(cx.theme().status().created_background)
23415            );
23416        },
23417    )
23418    .await;
23419
23420    // Multiple lines with edits
23421    assert_highlighted_edits(
23422        "First line\nSecond line\nThird line\nFourth line",
23423        vec![
23424            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23425            (
23426                Point::new(2, 0)..Point::new(2, 10),
23427                "New third line".to_string(),
23428            ),
23429            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23430        ],
23431        false,
23432        cx,
23433        |highlighted_edits, cx| {
23434            assert_eq!(
23435                highlighted_edits.text,
23436                "Second modified\nNew third line\nFourth updated line"
23437            );
23438            assert_eq!(highlighted_edits.highlights.len(), 3);
23439            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23440            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23441            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23442            for highlight in &highlighted_edits.highlights {
23443                assert_eq!(
23444                    highlight.1.background_color,
23445                    Some(cx.theme().status().created_background)
23446                );
23447            }
23448        },
23449    )
23450    .await;
23451}
23452
23453#[gpui::test]
23454async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23455    init_test(cx, |_| {});
23456
23457    // Deletion
23458    assert_highlighted_edits(
23459        "Hello, world!",
23460        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23461        true,
23462        cx,
23463        |highlighted_edits, cx| {
23464            assert_eq!(highlighted_edits.text, "Hello, world!");
23465            assert_eq!(highlighted_edits.highlights.len(), 1);
23466            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23467            assert_eq!(
23468                highlighted_edits.highlights[0].1.background_color,
23469                Some(cx.theme().status().deleted_background)
23470            );
23471        },
23472    )
23473    .await;
23474
23475    // Insertion
23476    assert_highlighted_edits(
23477        "Hello, world!",
23478        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23479        true,
23480        cx,
23481        |highlighted_edits, cx| {
23482            assert_eq!(highlighted_edits.highlights.len(), 1);
23483            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23484            assert_eq!(
23485                highlighted_edits.highlights[0].1.background_color,
23486                Some(cx.theme().status().created_background)
23487            );
23488        },
23489    )
23490    .await;
23491}
23492
23493async fn assert_highlighted_edits(
23494    text: &str,
23495    edits: Vec<(Range<Point>, String)>,
23496    include_deletions: bool,
23497    cx: &mut TestAppContext,
23498    assertion_fn: impl Fn(HighlightedText, &App),
23499) {
23500    let window = cx.add_window(|window, cx| {
23501        let buffer = MultiBuffer::build_simple(text, cx);
23502        Editor::new(EditorMode::full(), buffer, None, window, cx)
23503    });
23504    let cx = &mut VisualTestContext::from_window(*window, cx);
23505
23506    let (buffer, snapshot) = window
23507        .update(cx, |editor, _window, cx| {
23508            (
23509                editor.buffer().clone(),
23510                editor.buffer().read(cx).snapshot(cx),
23511            )
23512        })
23513        .unwrap();
23514
23515    let edits = edits
23516        .into_iter()
23517        .map(|(range, edit)| {
23518            (
23519                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23520                edit,
23521            )
23522        })
23523        .collect::<Vec<_>>();
23524
23525    let text_anchor_edits = edits
23526        .clone()
23527        .into_iter()
23528        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23529        .collect::<Vec<_>>();
23530
23531    let edit_preview = window
23532        .update(cx, |_, _window, cx| {
23533            buffer
23534                .read(cx)
23535                .as_singleton()
23536                .unwrap()
23537                .read(cx)
23538                .preview_edits(text_anchor_edits.into(), cx)
23539        })
23540        .unwrap()
23541        .await;
23542
23543    cx.update(|_window, cx| {
23544        let highlighted_edits = edit_prediction_edit_text(
23545            snapshot.as_singleton().unwrap().2,
23546            &edits,
23547            &edit_preview,
23548            include_deletions,
23549            cx,
23550        );
23551        assertion_fn(highlighted_edits, cx)
23552    });
23553}
23554
23555#[track_caller]
23556fn assert_breakpoint(
23557    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23558    path: &Arc<Path>,
23559    expected: Vec<(u32, Breakpoint)>,
23560) {
23561    if expected.is_empty() {
23562        assert!(!breakpoints.contains_key(path), "{}", path.display());
23563    } else {
23564        let mut breakpoint = breakpoints
23565            .get(path)
23566            .unwrap()
23567            .iter()
23568            .map(|breakpoint| {
23569                (
23570                    breakpoint.row,
23571                    Breakpoint {
23572                        message: breakpoint.message.clone(),
23573                        state: breakpoint.state,
23574                        condition: breakpoint.condition.clone(),
23575                        hit_condition: breakpoint.hit_condition.clone(),
23576                    },
23577                )
23578            })
23579            .collect::<Vec<_>>();
23580
23581        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23582
23583        assert_eq!(expected, breakpoint);
23584    }
23585}
23586
23587fn add_log_breakpoint_at_cursor(
23588    editor: &mut Editor,
23589    log_message: &str,
23590    window: &mut Window,
23591    cx: &mut Context<Editor>,
23592) {
23593    let (anchor, bp) = editor
23594        .breakpoints_at_cursors(window, cx)
23595        .first()
23596        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23597        .unwrap_or_else(|| {
23598            let snapshot = editor.snapshot(window, cx);
23599            let cursor_position: Point =
23600                editor.selections.newest(&snapshot.display_snapshot).head();
23601
23602            let breakpoint_position = snapshot
23603                .buffer_snapshot()
23604                .anchor_before(Point::new(cursor_position.row, 0));
23605
23606            (breakpoint_position, Breakpoint::new_log(log_message))
23607        });
23608
23609    editor.edit_breakpoint_at_anchor(
23610        anchor,
23611        bp,
23612        BreakpointEditAction::EditLogMessage(log_message.into()),
23613        cx,
23614    );
23615}
23616
23617#[gpui::test]
23618async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23619    init_test(cx, |_| {});
23620
23621    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23622    let fs = FakeFs::new(cx.executor());
23623    fs.insert_tree(
23624        path!("/a"),
23625        json!({
23626            "main.rs": sample_text,
23627        }),
23628    )
23629    .await;
23630    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23631    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23632    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23633
23634    let fs = FakeFs::new(cx.executor());
23635    fs.insert_tree(
23636        path!("/a"),
23637        json!({
23638            "main.rs": sample_text,
23639        }),
23640    )
23641    .await;
23642    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23643    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23644    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23645    let worktree_id = workspace
23646        .update(cx, |workspace, _window, cx| {
23647            workspace.project().update(cx, |project, cx| {
23648                project.worktrees(cx).next().unwrap().read(cx).id()
23649            })
23650        })
23651        .unwrap();
23652
23653    let buffer = project
23654        .update(cx, |project, cx| {
23655            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23656        })
23657        .await
23658        .unwrap();
23659
23660    let (editor, cx) = cx.add_window_view(|window, cx| {
23661        Editor::new(
23662            EditorMode::full(),
23663            MultiBuffer::build_from_buffer(buffer, cx),
23664            Some(project.clone()),
23665            window,
23666            cx,
23667        )
23668    });
23669
23670    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23671    let abs_path = project.read_with(cx, |project, cx| {
23672        project
23673            .absolute_path(&project_path, cx)
23674            .map(Arc::from)
23675            .unwrap()
23676    });
23677
23678    // assert we can add breakpoint on the first line
23679    editor.update_in(cx, |editor, window, cx| {
23680        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23681        editor.move_to_end(&MoveToEnd, window, cx);
23682        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23683    });
23684
23685    let breakpoints = editor.update(cx, |editor, cx| {
23686        editor
23687            .breakpoint_store()
23688            .as_ref()
23689            .unwrap()
23690            .read(cx)
23691            .all_source_breakpoints(cx)
23692    });
23693
23694    assert_eq!(1, breakpoints.len());
23695    assert_breakpoint(
23696        &breakpoints,
23697        &abs_path,
23698        vec![
23699            (0, Breakpoint::new_standard()),
23700            (3, Breakpoint::new_standard()),
23701        ],
23702    );
23703
23704    editor.update_in(cx, |editor, window, cx| {
23705        editor.move_to_beginning(&MoveToBeginning, window, cx);
23706        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23707    });
23708
23709    let breakpoints = editor.update(cx, |editor, cx| {
23710        editor
23711            .breakpoint_store()
23712            .as_ref()
23713            .unwrap()
23714            .read(cx)
23715            .all_source_breakpoints(cx)
23716    });
23717
23718    assert_eq!(1, breakpoints.len());
23719    assert_breakpoint(
23720        &breakpoints,
23721        &abs_path,
23722        vec![(3, Breakpoint::new_standard())],
23723    );
23724
23725    editor.update_in(cx, |editor, window, cx| {
23726        editor.move_to_end(&MoveToEnd, window, cx);
23727        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23728    });
23729
23730    let breakpoints = editor.update(cx, |editor, cx| {
23731        editor
23732            .breakpoint_store()
23733            .as_ref()
23734            .unwrap()
23735            .read(cx)
23736            .all_source_breakpoints(cx)
23737    });
23738
23739    assert_eq!(0, breakpoints.len());
23740    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23741}
23742
23743#[gpui::test]
23744async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23745    init_test(cx, |_| {});
23746
23747    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23748
23749    let fs = FakeFs::new(cx.executor());
23750    fs.insert_tree(
23751        path!("/a"),
23752        json!({
23753            "main.rs": sample_text,
23754        }),
23755    )
23756    .await;
23757    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23758    let (workspace, cx) =
23759        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23760
23761    let worktree_id = workspace.update(cx, |workspace, cx| {
23762        workspace.project().update(cx, |project, cx| {
23763            project.worktrees(cx).next().unwrap().read(cx).id()
23764        })
23765    });
23766
23767    let buffer = project
23768        .update(cx, |project, cx| {
23769            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23770        })
23771        .await
23772        .unwrap();
23773
23774    let (editor, cx) = cx.add_window_view(|window, cx| {
23775        Editor::new(
23776            EditorMode::full(),
23777            MultiBuffer::build_from_buffer(buffer, cx),
23778            Some(project.clone()),
23779            window,
23780            cx,
23781        )
23782    });
23783
23784    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23785    let abs_path = project.read_with(cx, |project, cx| {
23786        project
23787            .absolute_path(&project_path, cx)
23788            .map(Arc::from)
23789            .unwrap()
23790    });
23791
23792    editor.update_in(cx, |editor, window, cx| {
23793        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23794    });
23795
23796    let breakpoints = editor.update(cx, |editor, cx| {
23797        editor
23798            .breakpoint_store()
23799            .as_ref()
23800            .unwrap()
23801            .read(cx)
23802            .all_source_breakpoints(cx)
23803    });
23804
23805    assert_breakpoint(
23806        &breakpoints,
23807        &abs_path,
23808        vec![(0, Breakpoint::new_log("hello world"))],
23809    );
23810
23811    // Removing a log message from a log breakpoint should remove it
23812    editor.update_in(cx, |editor, window, cx| {
23813        add_log_breakpoint_at_cursor(editor, "", window, cx);
23814    });
23815
23816    let breakpoints = editor.update(cx, |editor, cx| {
23817        editor
23818            .breakpoint_store()
23819            .as_ref()
23820            .unwrap()
23821            .read(cx)
23822            .all_source_breakpoints(cx)
23823    });
23824
23825    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23826
23827    editor.update_in(cx, |editor, window, cx| {
23828        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23829        editor.move_to_end(&MoveToEnd, window, cx);
23830        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23831        // Not adding a log message to a standard breakpoint shouldn't remove it
23832        add_log_breakpoint_at_cursor(editor, "", window, cx);
23833    });
23834
23835    let breakpoints = editor.update(cx, |editor, cx| {
23836        editor
23837            .breakpoint_store()
23838            .as_ref()
23839            .unwrap()
23840            .read(cx)
23841            .all_source_breakpoints(cx)
23842    });
23843
23844    assert_breakpoint(
23845        &breakpoints,
23846        &abs_path,
23847        vec![
23848            (0, Breakpoint::new_standard()),
23849            (3, Breakpoint::new_standard()),
23850        ],
23851    );
23852
23853    editor.update_in(cx, |editor, window, cx| {
23854        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23855    });
23856
23857    let breakpoints = editor.update(cx, |editor, cx| {
23858        editor
23859            .breakpoint_store()
23860            .as_ref()
23861            .unwrap()
23862            .read(cx)
23863            .all_source_breakpoints(cx)
23864    });
23865
23866    assert_breakpoint(
23867        &breakpoints,
23868        &abs_path,
23869        vec![
23870            (0, Breakpoint::new_standard()),
23871            (3, Breakpoint::new_log("hello world")),
23872        ],
23873    );
23874
23875    editor.update_in(cx, |editor, window, cx| {
23876        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23877    });
23878
23879    let breakpoints = editor.update(cx, |editor, cx| {
23880        editor
23881            .breakpoint_store()
23882            .as_ref()
23883            .unwrap()
23884            .read(cx)
23885            .all_source_breakpoints(cx)
23886    });
23887
23888    assert_breakpoint(
23889        &breakpoints,
23890        &abs_path,
23891        vec![
23892            (0, Breakpoint::new_standard()),
23893            (3, Breakpoint::new_log("hello Earth!!")),
23894        ],
23895    );
23896}
23897
23898/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23899/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23900/// or when breakpoints were placed out of order. This tests for a regression too
23901#[gpui::test]
23902async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23903    init_test(cx, |_| {});
23904
23905    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23906    let fs = FakeFs::new(cx.executor());
23907    fs.insert_tree(
23908        path!("/a"),
23909        json!({
23910            "main.rs": sample_text,
23911        }),
23912    )
23913    .await;
23914    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23915    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23916    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23917
23918    let fs = FakeFs::new(cx.executor());
23919    fs.insert_tree(
23920        path!("/a"),
23921        json!({
23922            "main.rs": sample_text,
23923        }),
23924    )
23925    .await;
23926    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23927    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23928    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23929    let worktree_id = workspace
23930        .update(cx, |workspace, _window, cx| {
23931            workspace.project().update(cx, |project, cx| {
23932                project.worktrees(cx).next().unwrap().read(cx).id()
23933            })
23934        })
23935        .unwrap();
23936
23937    let buffer = project
23938        .update(cx, |project, cx| {
23939            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23940        })
23941        .await
23942        .unwrap();
23943
23944    let (editor, cx) = cx.add_window_view(|window, cx| {
23945        Editor::new(
23946            EditorMode::full(),
23947            MultiBuffer::build_from_buffer(buffer, cx),
23948            Some(project.clone()),
23949            window,
23950            cx,
23951        )
23952    });
23953
23954    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23955    let abs_path = project.read_with(cx, |project, cx| {
23956        project
23957            .absolute_path(&project_path, cx)
23958            .map(Arc::from)
23959            .unwrap()
23960    });
23961
23962    // assert we can add breakpoint on the first line
23963    editor.update_in(cx, |editor, window, cx| {
23964        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23965        editor.move_to_end(&MoveToEnd, window, cx);
23966        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23967        editor.move_up(&MoveUp, window, cx);
23968        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23969    });
23970
23971    let breakpoints = editor.update(cx, |editor, cx| {
23972        editor
23973            .breakpoint_store()
23974            .as_ref()
23975            .unwrap()
23976            .read(cx)
23977            .all_source_breakpoints(cx)
23978    });
23979
23980    assert_eq!(1, breakpoints.len());
23981    assert_breakpoint(
23982        &breakpoints,
23983        &abs_path,
23984        vec![
23985            (0, Breakpoint::new_standard()),
23986            (2, Breakpoint::new_standard()),
23987            (3, Breakpoint::new_standard()),
23988        ],
23989    );
23990
23991    editor.update_in(cx, |editor, window, cx| {
23992        editor.move_to_beginning(&MoveToBeginning, window, cx);
23993        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23994        editor.move_to_end(&MoveToEnd, window, cx);
23995        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23996        // Disabling a breakpoint that doesn't exist should do nothing
23997        editor.move_up(&MoveUp, window, cx);
23998        editor.move_up(&MoveUp, window, cx);
23999        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24000    });
24001
24002    let breakpoints = editor.update(cx, |editor, cx| {
24003        editor
24004            .breakpoint_store()
24005            .as_ref()
24006            .unwrap()
24007            .read(cx)
24008            .all_source_breakpoints(cx)
24009    });
24010
24011    let disable_breakpoint = {
24012        let mut bp = Breakpoint::new_standard();
24013        bp.state = BreakpointState::Disabled;
24014        bp
24015    };
24016
24017    assert_eq!(1, breakpoints.len());
24018    assert_breakpoint(
24019        &breakpoints,
24020        &abs_path,
24021        vec![
24022            (0, disable_breakpoint.clone()),
24023            (2, Breakpoint::new_standard()),
24024            (3, disable_breakpoint.clone()),
24025        ],
24026    );
24027
24028    editor.update_in(cx, |editor, window, cx| {
24029        editor.move_to_beginning(&MoveToBeginning, window, cx);
24030        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24031        editor.move_to_end(&MoveToEnd, window, cx);
24032        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
24033        editor.move_up(&MoveUp, window, cx);
24034        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
24035    });
24036
24037    let breakpoints = editor.update(cx, |editor, cx| {
24038        editor
24039            .breakpoint_store()
24040            .as_ref()
24041            .unwrap()
24042            .read(cx)
24043            .all_source_breakpoints(cx)
24044    });
24045
24046    assert_eq!(1, breakpoints.len());
24047    assert_breakpoint(
24048        &breakpoints,
24049        &abs_path,
24050        vec![
24051            (0, Breakpoint::new_standard()),
24052            (2, disable_breakpoint),
24053            (3, Breakpoint::new_standard()),
24054        ],
24055    );
24056}
24057
24058#[gpui::test]
24059async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
24060    init_test(cx, |_| {});
24061    let capabilities = lsp::ServerCapabilities {
24062        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
24063            prepare_provider: Some(true),
24064            work_done_progress_options: Default::default(),
24065        })),
24066        ..Default::default()
24067    };
24068    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24069
24070    cx.set_state(indoc! {"
24071        struct Fˇoo {}
24072    "});
24073
24074    cx.update_editor(|editor, _, cx| {
24075        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24076        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24077        editor.highlight_background::<DocumentHighlightRead>(
24078            &[highlight_range],
24079            |_, theme| theme.colors().editor_document_highlight_read_background,
24080            cx,
24081        );
24082    });
24083
24084    let mut prepare_rename_handler = cx
24085        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
24086            move |_, _, _| async move {
24087                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
24088                    start: lsp::Position {
24089                        line: 0,
24090                        character: 7,
24091                    },
24092                    end: lsp::Position {
24093                        line: 0,
24094                        character: 10,
24095                    },
24096                })))
24097            },
24098        );
24099    let prepare_rename_task = cx
24100        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24101        .expect("Prepare rename was not started");
24102    prepare_rename_handler.next().await.unwrap();
24103    prepare_rename_task.await.expect("Prepare rename failed");
24104
24105    let mut rename_handler =
24106        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24107            let edit = lsp::TextEdit {
24108                range: lsp::Range {
24109                    start: lsp::Position {
24110                        line: 0,
24111                        character: 7,
24112                    },
24113                    end: lsp::Position {
24114                        line: 0,
24115                        character: 10,
24116                    },
24117                },
24118                new_text: "FooRenamed".to_string(),
24119            };
24120            Ok(Some(lsp::WorkspaceEdit::new(
24121                // Specify the same edit twice
24122                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
24123            )))
24124        });
24125    let rename_task = cx
24126        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24127        .expect("Confirm rename was not started");
24128    rename_handler.next().await.unwrap();
24129    rename_task.await.expect("Confirm rename failed");
24130    cx.run_until_parked();
24131
24132    // Despite two edits, only one is actually applied as those are identical
24133    cx.assert_editor_state(indoc! {"
24134        struct FooRenamedˇ {}
24135    "});
24136}
24137
24138#[gpui::test]
24139async fn test_rename_without_prepare(cx: &mut TestAppContext) {
24140    init_test(cx, |_| {});
24141    // These capabilities indicate that the server does not support prepare rename.
24142    let capabilities = lsp::ServerCapabilities {
24143        rename_provider: Some(lsp::OneOf::Left(true)),
24144        ..Default::default()
24145    };
24146    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
24147
24148    cx.set_state(indoc! {"
24149        struct Fˇoo {}
24150    "});
24151
24152    cx.update_editor(|editor, _window, cx| {
24153        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
24154        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
24155        editor.highlight_background::<DocumentHighlightRead>(
24156            &[highlight_range],
24157            |_, theme| theme.colors().editor_document_highlight_read_background,
24158            cx,
24159        );
24160    });
24161
24162    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
24163        .expect("Prepare rename was not started")
24164        .await
24165        .expect("Prepare rename failed");
24166
24167    let mut rename_handler =
24168        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
24169            let edit = lsp::TextEdit {
24170                range: lsp::Range {
24171                    start: lsp::Position {
24172                        line: 0,
24173                        character: 7,
24174                    },
24175                    end: lsp::Position {
24176                        line: 0,
24177                        character: 10,
24178                    },
24179                },
24180                new_text: "FooRenamed".to_string(),
24181            };
24182            Ok(Some(lsp::WorkspaceEdit::new(
24183                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
24184            )))
24185        });
24186    let rename_task = cx
24187        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
24188        .expect("Confirm rename was not started");
24189    rename_handler.next().await.unwrap();
24190    rename_task.await.expect("Confirm rename failed");
24191    cx.run_until_parked();
24192
24193    // Correct range is renamed, as `surrounding_word` is used to find it.
24194    cx.assert_editor_state(indoc! {"
24195        struct FooRenamedˇ {}
24196    "});
24197}
24198
24199#[gpui::test]
24200async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
24201    init_test(cx, |_| {});
24202    let mut cx = EditorTestContext::new(cx).await;
24203
24204    let language = Arc::new(
24205        Language::new(
24206            LanguageConfig::default(),
24207            Some(tree_sitter_html::LANGUAGE.into()),
24208        )
24209        .with_brackets_query(
24210            r#"
24211            ("<" @open "/>" @close)
24212            ("</" @open ">" @close)
24213            ("<" @open ">" @close)
24214            ("\"" @open "\"" @close)
24215            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
24216        "#,
24217        )
24218        .unwrap(),
24219    );
24220    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
24221
24222    cx.set_state(indoc! {"
24223        <span>ˇ</span>
24224    "});
24225    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24226    cx.assert_editor_state(indoc! {"
24227        <span>
24228        ˇ
24229        </span>
24230    "});
24231
24232    cx.set_state(indoc! {"
24233        <span><span></span>ˇ</span>
24234    "});
24235    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24236    cx.assert_editor_state(indoc! {"
24237        <span><span></span>
24238        ˇ</span>
24239    "});
24240
24241    cx.set_state(indoc! {"
24242        <span>ˇ
24243        </span>
24244    "});
24245    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
24246    cx.assert_editor_state(indoc! {"
24247        <span>
24248        ˇ
24249        </span>
24250    "});
24251}
24252
24253#[gpui::test(iterations = 10)]
24254async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
24255    init_test(cx, |_| {});
24256
24257    let fs = FakeFs::new(cx.executor());
24258    fs.insert_tree(
24259        path!("/dir"),
24260        json!({
24261            "a.ts": "a",
24262        }),
24263    )
24264    .await;
24265
24266    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
24267    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24268    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24269
24270    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24271    language_registry.add(Arc::new(Language::new(
24272        LanguageConfig {
24273            name: "TypeScript".into(),
24274            matcher: LanguageMatcher {
24275                path_suffixes: vec!["ts".to_string()],
24276                ..Default::default()
24277            },
24278            ..Default::default()
24279        },
24280        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24281    )));
24282    let mut fake_language_servers = language_registry.register_fake_lsp(
24283        "TypeScript",
24284        FakeLspAdapter {
24285            capabilities: lsp::ServerCapabilities {
24286                code_lens_provider: Some(lsp::CodeLensOptions {
24287                    resolve_provider: Some(true),
24288                }),
24289                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24290                    commands: vec!["_the/command".to_string()],
24291                    ..lsp::ExecuteCommandOptions::default()
24292                }),
24293                ..lsp::ServerCapabilities::default()
24294            },
24295            ..FakeLspAdapter::default()
24296        },
24297    );
24298
24299    let editor = workspace
24300        .update(cx, |workspace, window, cx| {
24301            workspace.open_abs_path(
24302                PathBuf::from(path!("/dir/a.ts")),
24303                OpenOptions::default(),
24304                window,
24305                cx,
24306            )
24307        })
24308        .unwrap()
24309        .await
24310        .unwrap()
24311        .downcast::<Editor>()
24312        .unwrap();
24313    cx.executor().run_until_parked();
24314
24315    let fake_server = fake_language_servers.next().await.unwrap();
24316
24317    let buffer = editor.update(cx, |editor, cx| {
24318        editor
24319            .buffer()
24320            .read(cx)
24321            .as_singleton()
24322            .expect("have opened a single file by path")
24323    });
24324
24325    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24326    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24327    drop(buffer_snapshot);
24328    let actions = cx
24329        .update_window(*workspace, |_, window, cx| {
24330            project.code_actions(&buffer, anchor..anchor, window, cx)
24331        })
24332        .unwrap();
24333
24334    fake_server
24335        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24336            Ok(Some(vec![
24337                lsp::CodeLens {
24338                    range: lsp::Range::default(),
24339                    command: Some(lsp::Command {
24340                        title: "Code lens command".to_owned(),
24341                        command: "_the/command".to_owned(),
24342                        arguments: None,
24343                    }),
24344                    data: None,
24345                },
24346                lsp::CodeLens {
24347                    range: lsp::Range::default(),
24348                    command: Some(lsp::Command {
24349                        title: "Command not in capabilities".to_owned(),
24350                        command: "not in capabilities".to_owned(),
24351                        arguments: None,
24352                    }),
24353                    data: None,
24354                },
24355                lsp::CodeLens {
24356                    range: lsp::Range {
24357                        start: lsp::Position {
24358                            line: 1,
24359                            character: 1,
24360                        },
24361                        end: lsp::Position {
24362                            line: 1,
24363                            character: 1,
24364                        },
24365                    },
24366                    command: Some(lsp::Command {
24367                        title: "Command not in range".to_owned(),
24368                        command: "_the/command".to_owned(),
24369                        arguments: None,
24370                    }),
24371                    data: None,
24372                },
24373            ]))
24374        })
24375        .next()
24376        .await;
24377
24378    let actions = actions.await.unwrap();
24379    assert_eq!(
24380        actions.len(),
24381        1,
24382        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24383    );
24384    let action = actions[0].clone();
24385    let apply = project.update(cx, |project, cx| {
24386        project.apply_code_action(buffer.clone(), action, true, cx)
24387    });
24388
24389    // Resolving the code action does not populate its edits. In absence of
24390    // edits, we must execute the given command.
24391    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24392        |mut lens, _| async move {
24393            let lens_command = lens.command.as_mut().expect("should have a command");
24394            assert_eq!(lens_command.title, "Code lens command");
24395            lens_command.arguments = Some(vec![json!("the-argument")]);
24396            Ok(lens)
24397        },
24398    );
24399
24400    // While executing the command, the language server sends the editor
24401    // a `workspaceEdit` request.
24402    fake_server
24403        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24404            let fake = fake_server.clone();
24405            move |params, _| {
24406                assert_eq!(params.command, "_the/command");
24407                let fake = fake.clone();
24408                async move {
24409                    fake.server
24410                        .request::<lsp::request::ApplyWorkspaceEdit>(
24411                            lsp::ApplyWorkspaceEditParams {
24412                                label: None,
24413                                edit: lsp::WorkspaceEdit {
24414                                    changes: Some(
24415                                        [(
24416                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24417                                            vec![lsp::TextEdit {
24418                                                range: lsp::Range::new(
24419                                                    lsp::Position::new(0, 0),
24420                                                    lsp::Position::new(0, 0),
24421                                                ),
24422                                                new_text: "X".into(),
24423                                            }],
24424                                        )]
24425                                        .into_iter()
24426                                        .collect(),
24427                                    ),
24428                                    ..lsp::WorkspaceEdit::default()
24429                                },
24430                            },
24431                        )
24432                        .await
24433                        .into_response()
24434                        .unwrap();
24435                    Ok(Some(json!(null)))
24436                }
24437            }
24438        })
24439        .next()
24440        .await;
24441
24442    // Applying the code lens command returns a project transaction containing the edits
24443    // sent by the language server in its `workspaceEdit` request.
24444    let transaction = apply.await.unwrap();
24445    assert!(transaction.0.contains_key(&buffer));
24446    buffer.update(cx, |buffer, cx| {
24447        assert_eq!(buffer.text(), "Xa");
24448        buffer.undo(cx);
24449        assert_eq!(buffer.text(), "a");
24450    });
24451
24452    let actions_after_edits = cx
24453        .update_window(*workspace, |_, window, cx| {
24454            project.code_actions(&buffer, anchor..anchor, window, cx)
24455        })
24456        .unwrap()
24457        .await
24458        .unwrap();
24459    assert_eq!(
24460        actions, actions_after_edits,
24461        "For the same selection, same code lens actions should be returned"
24462    );
24463
24464    let _responses =
24465        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24466            panic!("No more code lens requests are expected");
24467        });
24468    editor.update_in(cx, |editor, window, cx| {
24469        editor.select_all(&SelectAll, window, cx);
24470    });
24471    cx.executor().run_until_parked();
24472    let new_actions = cx
24473        .update_window(*workspace, |_, window, cx| {
24474            project.code_actions(&buffer, anchor..anchor, window, cx)
24475        })
24476        .unwrap()
24477        .await
24478        .unwrap();
24479    assert_eq!(
24480        actions, new_actions,
24481        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24482    );
24483}
24484
24485#[gpui::test]
24486async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24487    init_test(cx, |_| {});
24488
24489    let fs = FakeFs::new(cx.executor());
24490    let main_text = r#"fn main() {
24491println!("1");
24492println!("2");
24493println!("3");
24494println!("4");
24495println!("5");
24496}"#;
24497    let lib_text = "mod foo {}";
24498    fs.insert_tree(
24499        path!("/a"),
24500        json!({
24501            "lib.rs": lib_text,
24502            "main.rs": main_text,
24503        }),
24504    )
24505    .await;
24506
24507    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24508    let (workspace, cx) =
24509        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24510    let worktree_id = workspace.update(cx, |workspace, cx| {
24511        workspace.project().update(cx, |project, cx| {
24512            project.worktrees(cx).next().unwrap().read(cx).id()
24513        })
24514    });
24515
24516    let expected_ranges = vec![
24517        Point::new(0, 0)..Point::new(0, 0),
24518        Point::new(1, 0)..Point::new(1, 1),
24519        Point::new(2, 0)..Point::new(2, 2),
24520        Point::new(3, 0)..Point::new(3, 3),
24521    ];
24522
24523    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24524    let editor_1 = workspace
24525        .update_in(cx, |workspace, window, cx| {
24526            workspace.open_path(
24527                (worktree_id, rel_path("main.rs")),
24528                Some(pane_1.downgrade()),
24529                true,
24530                window,
24531                cx,
24532            )
24533        })
24534        .unwrap()
24535        .await
24536        .downcast::<Editor>()
24537        .unwrap();
24538    pane_1.update(cx, |pane, cx| {
24539        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24540        open_editor.update(cx, |editor, cx| {
24541            assert_eq!(
24542                editor.display_text(cx),
24543                main_text,
24544                "Original main.rs text on initial open",
24545            );
24546            assert_eq!(
24547                editor
24548                    .selections
24549                    .all::<Point>(&editor.display_snapshot(cx))
24550                    .into_iter()
24551                    .map(|s| s.range())
24552                    .collect::<Vec<_>>(),
24553                vec![Point::zero()..Point::zero()],
24554                "Default selections on initial open",
24555            );
24556        })
24557    });
24558    editor_1.update_in(cx, |editor, window, cx| {
24559        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24560            s.select_ranges(expected_ranges.clone());
24561        });
24562    });
24563
24564    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24565        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24566    });
24567    let editor_2 = workspace
24568        .update_in(cx, |workspace, window, cx| {
24569            workspace.open_path(
24570                (worktree_id, rel_path("main.rs")),
24571                Some(pane_2.downgrade()),
24572                true,
24573                window,
24574                cx,
24575            )
24576        })
24577        .unwrap()
24578        .await
24579        .downcast::<Editor>()
24580        .unwrap();
24581    pane_2.update(cx, |pane, cx| {
24582        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24583        open_editor.update(cx, |editor, cx| {
24584            assert_eq!(
24585                editor.display_text(cx),
24586                main_text,
24587                "Original main.rs text on initial open in another panel",
24588            );
24589            assert_eq!(
24590                editor
24591                    .selections
24592                    .all::<Point>(&editor.display_snapshot(cx))
24593                    .into_iter()
24594                    .map(|s| s.range())
24595                    .collect::<Vec<_>>(),
24596                vec![Point::zero()..Point::zero()],
24597                "Default selections on initial open in another panel",
24598            );
24599        })
24600    });
24601
24602    editor_2.update_in(cx, |editor, window, cx| {
24603        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24604    });
24605
24606    let _other_editor_1 = workspace
24607        .update_in(cx, |workspace, window, cx| {
24608            workspace.open_path(
24609                (worktree_id, rel_path("lib.rs")),
24610                Some(pane_1.downgrade()),
24611                true,
24612                window,
24613                cx,
24614            )
24615        })
24616        .unwrap()
24617        .await
24618        .downcast::<Editor>()
24619        .unwrap();
24620    pane_1
24621        .update_in(cx, |pane, window, cx| {
24622            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24623        })
24624        .await
24625        .unwrap();
24626    drop(editor_1);
24627    pane_1.update(cx, |pane, cx| {
24628        pane.active_item()
24629            .unwrap()
24630            .downcast::<Editor>()
24631            .unwrap()
24632            .update(cx, |editor, cx| {
24633                assert_eq!(
24634                    editor.display_text(cx),
24635                    lib_text,
24636                    "Other file should be open and active",
24637                );
24638            });
24639        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24640    });
24641
24642    let _other_editor_2 = workspace
24643        .update_in(cx, |workspace, window, cx| {
24644            workspace.open_path(
24645                (worktree_id, rel_path("lib.rs")),
24646                Some(pane_2.downgrade()),
24647                true,
24648                window,
24649                cx,
24650            )
24651        })
24652        .unwrap()
24653        .await
24654        .downcast::<Editor>()
24655        .unwrap();
24656    pane_2
24657        .update_in(cx, |pane, window, cx| {
24658            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24659        })
24660        .await
24661        .unwrap();
24662    drop(editor_2);
24663    pane_2.update(cx, |pane, cx| {
24664        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24665        open_editor.update(cx, |editor, cx| {
24666            assert_eq!(
24667                editor.display_text(cx),
24668                lib_text,
24669                "Other file should be open and active in another panel too",
24670            );
24671        });
24672        assert_eq!(
24673            pane.items().count(),
24674            1,
24675            "No other editors should be open in another pane",
24676        );
24677    });
24678
24679    let _editor_1_reopened = workspace
24680        .update_in(cx, |workspace, window, cx| {
24681            workspace.open_path(
24682                (worktree_id, rel_path("main.rs")),
24683                Some(pane_1.downgrade()),
24684                true,
24685                window,
24686                cx,
24687            )
24688        })
24689        .unwrap()
24690        .await
24691        .downcast::<Editor>()
24692        .unwrap();
24693    let _editor_2_reopened = workspace
24694        .update_in(cx, |workspace, window, cx| {
24695            workspace.open_path(
24696                (worktree_id, rel_path("main.rs")),
24697                Some(pane_2.downgrade()),
24698                true,
24699                window,
24700                cx,
24701            )
24702        })
24703        .unwrap()
24704        .await
24705        .downcast::<Editor>()
24706        .unwrap();
24707    pane_1.update(cx, |pane, cx| {
24708        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24709        open_editor.update(cx, |editor, cx| {
24710            assert_eq!(
24711                editor.display_text(cx),
24712                main_text,
24713                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24714            );
24715            assert_eq!(
24716                editor
24717                    .selections
24718                    .all::<Point>(&editor.display_snapshot(cx))
24719                    .into_iter()
24720                    .map(|s| s.range())
24721                    .collect::<Vec<_>>(),
24722                expected_ranges,
24723                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24724            );
24725        })
24726    });
24727    pane_2.update(cx, |pane, cx| {
24728        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24729        open_editor.update(cx, |editor, cx| {
24730            assert_eq!(
24731                editor.display_text(cx),
24732                r#"fn main() {
24733⋯rintln!("1");
24734⋯intln!("2");
24735⋯ntln!("3");
24736println!("4");
24737println!("5");
24738}"#,
24739                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24740            );
24741            assert_eq!(
24742                editor
24743                    .selections
24744                    .all::<Point>(&editor.display_snapshot(cx))
24745                    .into_iter()
24746                    .map(|s| s.range())
24747                    .collect::<Vec<_>>(),
24748                vec![Point::zero()..Point::zero()],
24749                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24750            );
24751        })
24752    });
24753}
24754
24755#[gpui::test]
24756async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24757    init_test(cx, |_| {});
24758
24759    let fs = FakeFs::new(cx.executor());
24760    let main_text = r#"fn main() {
24761println!("1");
24762println!("2");
24763println!("3");
24764println!("4");
24765println!("5");
24766}"#;
24767    let lib_text = "mod foo {}";
24768    fs.insert_tree(
24769        path!("/a"),
24770        json!({
24771            "lib.rs": lib_text,
24772            "main.rs": main_text,
24773        }),
24774    )
24775    .await;
24776
24777    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24778    let (workspace, cx) =
24779        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24780    let worktree_id = workspace.update(cx, |workspace, cx| {
24781        workspace.project().update(cx, |project, cx| {
24782            project.worktrees(cx).next().unwrap().read(cx).id()
24783        })
24784    });
24785
24786    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24787    let editor = workspace
24788        .update_in(cx, |workspace, window, cx| {
24789            workspace.open_path(
24790                (worktree_id, rel_path("main.rs")),
24791                Some(pane.downgrade()),
24792                true,
24793                window,
24794                cx,
24795            )
24796        })
24797        .unwrap()
24798        .await
24799        .downcast::<Editor>()
24800        .unwrap();
24801    pane.update(cx, |pane, cx| {
24802        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24803        open_editor.update(cx, |editor, cx| {
24804            assert_eq!(
24805                editor.display_text(cx),
24806                main_text,
24807                "Original main.rs text on initial open",
24808            );
24809        })
24810    });
24811    editor.update_in(cx, |editor, window, cx| {
24812        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24813    });
24814
24815    cx.update_global(|store: &mut SettingsStore, cx| {
24816        store.update_user_settings(cx, |s| {
24817            s.workspace.restore_on_file_reopen = Some(false);
24818        });
24819    });
24820    editor.update_in(cx, |editor, window, cx| {
24821        editor.fold_ranges(
24822            vec![
24823                Point::new(1, 0)..Point::new(1, 1),
24824                Point::new(2, 0)..Point::new(2, 2),
24825                Point::new(3, 0)..Point::new(3, 3),
24826            ],
24827            false,
24828            window,
24829            cx,
24830        );
24831    });
24832    pane.update_in(cx, |pane, window, cx| {
24833        pane.close_all_items(&CloseAllItems::default(), window, cx)
24834    })
24835    .await
24836    .unwrap();
24837    pane.update(cx, |pane, _| {
24838        assert!(pane.active_item().is_none());
24839    });
24840    cx.update_global(|store: &mut SettingsStore, cx| {
24841        store.update_user_settings(cx, |s| {
24842            s.workspace.restore_on_file_reopen = Some(true);
24843        });
24844    });
24845
24846    let _editor_reopened = workspace
24847        .update_in(cx, |workspace, window, cx| {
24848            workspace.open_path(
24849                (worktree_id, rel_path("main.rs")),
24850                Some(pane.downgrade()),
24851                true,
24852                window,
24853                cx,
24854            )
24855        })
24856        .unwrap()
24857        .await
24858        .downcast::<Editor>()
24859        .unwrap();
24860    pane.update(cx, |pane, cx| {
24861        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24862        open_editor.update(cx, |editor, cx| {
24863            assert_eq!(
24864                editor.display_text(cx),
24865                main_text,
24866                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24867            );
24868        })
24869    });
24870}
24871
24872#[gpui::test]
24873async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24874    struct EmptyModalView {
24875        focus_handle: gpui::FocusHandle,
24876    }
24877    impl EventEmitter<DismissEvent> for EmptyModalView {}
24878    impl Render for EmptyModalView {
24879        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24880            div()
24881        }
24882    }
24883    impl Focusable for EmptyModalView {
24884        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24885            self.focus_handle.clone()
24886        }
24887    }
24888    impl workspace::ModalView for EmptyModalView {}
24889    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24890        EmptyModalView {
24891            focus_handle: cx.focus_handle(),
24892        }
24893    }
24894
24895    init_test(cx, |_| {});
24896
24897    let fs = FakeFs::new(cx.executor());
24898    let project = Project::test(fs, [], cx).await;
24899    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24900    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24901    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24902    let editor = cx.new_window_entity(|window, cx| {
24903        Editor::new(
24904            EditorMode::full(),
24905            buffer,
24906            Some(project.clone()),
24907            window,
24908            cx,
24909        )
24910    });
24911    workspace
24912        .update(cx, |workspace, window, cx| {
24913            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24914        })
24915        .unwrap();
24916    editor.update_in(cx, |editor, window, cx| {
24917        editor.open_context_menu(&OpenContextMenu, window, cx);
24918        assert!(editor.mouse_context_menu.is_some());
24919    });
24920    workspace
24921        .update(cx, |workspace, window, cx| {
24922            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24923        })
24924        .unwrap();
24925    cx.read(|cx| {
24926        assert!(editor.read(cx).mouse_context_menu.is_none());
24927    });
24928}
24929
24930fn set_linked_edit_ranges(
24931    opening: (Point, Point),
24932    closing: (Point, Point),
24933    editor: &mut Editor,
24934    cx: &mut Context<Editor>,
24935) {
24936    let Some((buffer, _)) = editor
24937        .buffer
24938        .read(cx)
24939        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24940    else {
24941        panic!("Failed to get buffer for selection position");
24942    };
24943    let buffer = buffer.read(cx);
24944    let buffer_id = buffer.remote_id();
24945    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24946    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24947    let mut linked_ranges = HashMap::default();
24948    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24949    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24950}
24951
24952#[gpui::test]
24953async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24954    init_test(cx, |_| {});
24955
24956    let fs = FakeFs::new(cx.executor());
24957    fs.insert_file(path!("/file.html"), Default::default())
24958        .await;
24959
24960    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24961
24962    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24963    let html_language = Arc::new(Language::new(
24964        LanguageConfig {
24965            name: "HTML".into(),
24966            matcher: LanguageMatcher {
24967                path_suffixes: vec!["html".to_string()],
24968                ..LanguageMatcher::default()
24969            },
24970            brackets: BracketPairConfig {
24971                pairs: vec![BracketPair {
24972                    start: "<".into(),
24973                    end: ">".into(),
24974                    close: true,
24975                    ..Default::default()
24976                }],
24977                ..Default::default()
24978            },
24979            ..Default::default()
24980        },
24981        Some(tree_sitter_html::LANGUAGE.into()),
24982    ));
24983    language_registry.add(html_language);
24984    let mut fake_servers = language_registry.register_fake_lsp(
24985        "HTML",
24986        FakeLspAdapter {
24987            capabilities: lsp::ServerCapabilities {
24988                completion_provider: Some(lsp::CompletionOptions {
24989                    resolve_provider: Some(true),
24990                    ..Default::default()
24991                }),
24992                ..Default::default()
24993            },
24994            ..Default::default()
24995        },
24996    );
24997
24998    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24999    let cx = &mut VisualTestContext::from_window(*workspace, cx);
25000
25001    let worktree_id = workspace
25002        .update(cx, |workspace, _window, cx| {
25003            workspace.project().update(cx, |project, cx| {
25004                project.worktrees(cx).next().unwrap().read(cx).id()
25005            })
25006        })
25007        .unwrap();
25008    project
25009        .update(cx, |project, cx| {
25010            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
25011        })
25012        .await
25013        .unwrap();
25014    let editor = workspace
25015        .update(cx, |workspace, window, cx| {
25016            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
25017        })
25018        .unwrap()
25019        .await
25020        .unwrap()
25021        .downcast::<Editor>()
25022        .unwrap();
25023
25024    let fake_server = fake_servers.next().await.unwrap();
25025    editor.update_in(cx, |editor, window, cx| {
25026        editor.set_text("<ad></ad>", window, cx);
25027        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25028            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
25029        });
25030        set_linked_edit_ranges(
25031            (Point::new(0, 1), Point::new(0, 3)),
25032            (Point::new(0, 6), Point::new(0, 8)),
25033            editor,
25034            cx,
25035        );
25036    });
25037    let mut completion_handle =
25038        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25039            Ok(Some(lsp::CompletionResponse::Array(vec![
25040                lsp::CompletionItem {
25041                    label: "head".to_string(),
25042                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
25043                        lsp::InsertReplaceEdit {
25044                            new_text: "head".to_string(),
25045                            insert: lsp::Range::new(
25046                                lsp::Position::new(0, 1),
25047                                lsp::Position::new(0, 3),
25048                            ),
25049                            replace: lsp::Range::new(
25050                                lsp::Position::new(0, 1),
25051                                lsp::Position::new(0, 3),
25052                            ),
25053                        },
25054                    )),
25055                    ..Default::default()
25056                },
25057            ])))
25058        });
25059    editor.update_in(cx, |editor, window, cx| {
25060        editor.show_completions(&ShowCompletions, window, cx);
25061    });
25062    cx.run_until_parked();
25063    completion_handle.next().await.unwrap();
25064    editor.update(cx, |editor, _| {
25065        assert!(
25066            editor.context_menu_visible(),
25067            "Completion menu should be visible"
25068        );
25069    });
25070    editor.update_in(cx, |editor, window, cx| {
25071        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
25072    });
25073    cx.executor().run_until_parked();
25074    editor.update(cx, |editor, cx| {
25075        assert_eq!(editor.text(cx), "<head></head>");
25076    });
25077}
25078
25079#[gpui::test]
25080async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
25081    init_test(cx, |_| {});
25082
25083    let mut cx = EditorTestContext::new(cx).await;
25084    let language = Arc::new(Language::new(
25085        LanguageConfig {
25086            name: "TSX".into(),
25087            matcher: LanguageMatcher {
25088                path_suffixes: vec!["tsx".to_string()],
25089                ..LanguageMatcher::default()
25090            },
25091            brackets: BracketPairConfig {
25092                pairs: vec![BracketPair {
25093                    start: "<".into(),
25094                    end: ">".into(),
25095                    close: true,
25096                    ..Default::default()
25097                }],
25098                ..Default::default()
25099            },
25100            linked_edit_characters: HashSet::from_iter(['.']),
25101            ..Default::default()
25102        },
25103        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
25104    ));
25105    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25106
25107    // Test typing > does not extend linked pair
25108    cx.set_state("<divˇ<div></div>");
25109    cx.update_editor(|editor, _, cx| {
25110        set_linked_edit_ranges(
25111            (Point::new(0, 1), Point::new(0, 4)),
25112            (Point::new(0, 11), Point::new(0, 14)),
25113            editor,
25114            cx,
25115        );
25116    });
25117    cx.update_editor(|editor, window, cx| {
25118        editor.handle_input(">", window, cx);
25119    });
25120    cx.assert_editor_state("<div>ˇ<div></div>");
25121
25122    // Test typing . do extend linked pair
25123    cx.set_state("<Animatedˇ></Animated>");
25124    cx.update_editor(|editor, _, cx| {
25125        set_linked_edit_ranges(
25126            (Point::new(0, 1), Point::new(0, 9)),
25127            (Point::new(0, 12), Point::new(0, 20)),
25128            editor,
25129            cx,
25130        );
25131    });
25132    cx.update_editor(|editor, window, cx| {
25133        editor.handle_input(".", window, cx);
25134    });
25135    cx.assert_editor_state("<Animated.ˇ></Animated.>");
25136    cx.update_editor(|editor, _, cx| {
25137        set_linked_edit_ranges(
25138            (Point::new(0, 1), Point::new(0, 10)),
25139            (Point::new(0, 13), Point::new(0, 21)),
25140            editor,
25141            cx,
25142        );
25143    });
25144    cx.update_editor(|editor, window, cx| {
25145        editor.handle_input("V", window, cx);
25146    });
25147    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
25148}
25149
25150#[gpui::test]
25151async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
25152    init_test(cx, |_| {});
25153
25154    let fs = FakeFs::new(cx.executor());
25155    fs.insert_tree(
25156        path!("/root"),
25157        json!({
25158            "a": {
25159                "main.rs": "fn main() {}",
25160            },
25161            "foo": {
25162                "bar": {
25163                    "external_file.rs": "pub mod external {}",
25164                }
25165            }
25166        }),
25167    )
25168    .await;
25169
25170    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
25171    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
25172    language_registry.add(rust_lang());
25173    let _fake_servers = language_registry.register_fake_lsp(
25174        "Rust",
25175        FakeLspAdapter {
25176            ..FakeLspAdapter::default()
25177        },
25178    );
25179    let (workspace, cx) =
25180        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
25181    let worktree_id = workspace.update(cx, |workspace, cx| {
25182        workspace.project().update(cx, |project, cx| {
25183            project.worktrees(cx).next().unwrap().read(cx).id()
25184        })
25185    });
25186
25187    let assert_language_servers_count =
25188        |expected: usize, context: &str, cx: &mut VisualTestContext| {
25189            project.update(cx, |project, cx| {
25190                let current = project
25191                    .lsp_store()
25192                    .read(cx)
25193                    .as_local()
25194                    .unwrap()
25195                    .language_servers
25196                    .len();
25197                assert_eq!(expected, current, "{context}");
25198            });
25199        };
25200
25201    assert_language_servers_count(
25202        0,
25203        "No servers should be running before any file is open",
25204        cx,
25205    );
25206    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
25207    let main_editor = workspace
25208        .update_in(cx, |workspace, window, cx| {
25209            workspace.open_path(
25210                (worktree_id, rel_path("main.rs")),
25211                Some(pane.downgrade()),
25212                true,
25213                window,
25214                cx,
25215            )
25216        })
25217        .unwrap()
25218        .await
25219        .downcast::<Editor>()
25220        .unwrap();
25221    pane.update(cx, |pane, cx| {
25222        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25223        open_editor.update(cx, |editor, cx| {
25224            assert_eq!(
25225                editor.display_text(cx),
25226                "fn main() {}",
25227                "Original main.rs text on initial open",
25228            );
25229        });
25230        assert_eq!(open_editor, main_editor);
25231    });
25232    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
25233
25234    let external_editor = workspace
25235        .update_in(cx, |workspace, window, cx| {
25236            workspace.open_abs_path(
25237                PathBuf::from("/root/foo/bar/external_file.rs"),
25238                OpenOptions::default(),
25239                window,
25240                cx,
25241            )
25242        })
25243        .await
25244        .expect("opening external file")
25245        .downcast::<Editor>()
25246        .expect("downcasted external file's open element to editor");
25247    pane.update(cx, |pane, cx| {
25248        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25249        open_editor.update(cx, |editor, cx| {
25250            assert_eq!(
25251                editor.display_text(cx),
25252                "pub mod external {}",
25253                "External file is open now",
25254            );
25255        });
25256        assert_eq!(open_editor, external_editor);
25257    });
25258    assert_language_servers_count(
25259        1,
25260        "Second, external, *.rs file should join the existing server",
25261        cx,
25262    );
25263
25264    pane.update_in(cx, |pane, window, cx| {
25265        pane.close_active_item(&CloseActiveItem::default(), window, cx)
25266    })
25267    .await
25268    .unwrap();
25269    pane.update_in(cx, |pane, window, cx| {
25270        pane.navigate_backward(&Default::default(), window, cx);
25271    });
25272    cx.run_until_parked();
25273    pane.update(cx, |pane, cx| {
25274        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25275        open_editor.update(cx, |editor, cx| {
25276            assert_eq!(
25277                editor.display_text(cx),
25278                "pub mod external {}",
25279                "External file is open now",
25280            );
25281        });
25282    });
25283    assert_language_servers_count(
25284        1,
25285        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25286        cx,
25287    );
25288
25289    cx.update(|_, cx| {
25290        workspace::reload(cx);
25291    });
25292    assert_language_servers_count(
25293        1,
25294        "After reloading the worktree with local and external files opened, only one project should be started",
25295        cx,
25296    );
25297}
25298
25299#[gpui::test]
25300async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25301    init_test(cx, |_| {});
25302
25303    let mut cx = EditorTestContext::new(cx).await;
25304    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25305    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25306
25307    // test cursor move to start of each line on tab
25308    // for `if`, `elif`, `else`, `while`, `with` and `for`
25309    cx.set_state(indoc! {"
25310        def main():
25311        ˇ    for item in items:
25312        ˇ        while item.active:
25313        ˇ            if item.value > 10:
25314        ˇ                continue
25315        ˇ            elif item.value < 0:
25316        ˇ                break
25317        ˇ            else:
25318        ˇ                with item.context() as ctx:
25319        ˇ                    yield count
25320        ˇ        else:
25321        ˇ            log('while else')
25322        ˇ    else:
25323        ˇ        log('for else')
25324    "});
25325    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25326    cx.assert_editor_state(indoc! {"
25327        def main():
25328            ˇfor item in items:
25329                ˇwhile item.active:
25330                    ˇif item.value > 10:
25331                        ˇcontinue
25332                    ˇelif item.value < 0:
25333                        ˇbreak
25334                    ˇelse:
25335                        ˇwith item.context() as ctx:
25336                            ˇyield count
25337                ˇelse:
25338                    ˇlog('while else')
25339            ˇelse:
25340                ˇlog('for else')
25341    "});
25342    // test relative indent is preserved when tab
25343    // for `if`, `elif`, `else`, `while`, `with` and `for`
25344    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25345    cx.assert_editor_state(indoc! {"
25346        def main():
25347                ˇfor item in items:
25348                    ˇwhile item.active:
25349                        ˇif item.value > 10:
25350                            ˇcontinue
25351                        ˇelif item.value < 0:
25352                            ˇbreak
25353                        ˇelse:
25354                            ˇwith item.context() as ctx:
25355                                ˇyield count
25356                    ˇelse:
25357                        ˇlog('while else')
25358                ˇelse:
25359                    ˇlog('for else')
25360    "});
25361
25362    // test cursor move to start of each line on tab
25363    // for `try`, `except`, `else`, `finally`, `match` and `def`
25364    cx.set_state(indoc! {"
25365        def main():
25366        ˇ    try:
25367        ˇ        fetch()
25368        ˇ    except ValueError:
25369        ˇ        handle_error()
25370        ˇ    else:
25371        ˇ        match value:
25372        ˇ            case _:
25373        ˇ    finally:
25374        ˇ        def status():
25375        ˇ            return 0
25376    "});
25377    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25378    cx.assert_editor_state(indoc! {"
25379        def main():
25380            ˇtry:
25381                ˇfetch()
25382            ˇexcept ValueError:
25383                ˇhandle_error()
25384            ˇelse:
25385                ˇmatch value:
25386                    ˇcase _:
25387            ˇfinally:
25388                ˇdef status():
25389                    ˇreturn 0
25390    "});
25391    // test relative indent is preserved when tab
25392    // for `try`, `except`, `else`, `finally`, `match` and `def`
25393    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25394    cx.assert_editor_state(indoc! {"
25395        def main():
25396                ˇtry:
25397                    ˇfetch()
25398                ˇexcept ValueError:
25399                    ˇhandle_error()
25400                ˇelse:
25401                    ˇmatch value:
25402                        ˇcase _:
25403                ˇfinally:
25404                    ˇdef status():
25405                        ˇreturn 0
25406    "});
25407}
25408
25409#[gpui::test]
25410async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25411    init_test(cx, |_| {});
25412
25413    let mut cx = EditorTestContext::new(cx).await;
25414    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25415    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25416
25417    // test `else` auto outdents when typed inside `if` block
25418    cx.set_state(indoc! {"
25419        def main():
25420            if i == 2:
25421                return
25422                ˇ
25423    "});
25424    cx.update_editor(|editor, window, cx| {
25425        editor.handle_input("else:", window, cx);
25426    });
25427    cx.assert_editor_state(indoc! {"
25428        def main():
25429            if i == 2:
25430                return
25431            else:ˇ
25432    "});
25433
25434    // test `except` auto outdents when typed inside `try` block
25435    cx.set_state(indoc! {"
25436        def main():
25437            try:
25438                i = 2
25439                ˇ
25440    "});
25441    cx.update_editor(|editor, window, cx| {
25442        editor.handle_input("except:", window, cx);
25443    });
25444    cx.assert_editor_state(indoc! {"
25445        def main():
25446            try:
25447                i = 2
25448            except:ˇ
25449    "});
25450
25451    // test `else` auto outdents when typed inside `except` block
25452    cx.set_state(indoc! {"
25453        def main():
25454            try:
25455                i = 2
25456            except:
25457                j = 2
25458                ˇ
25459    "});
25460    cx.update_editor(|editor, window, cx| {
25461        editor.handle_input("else:", window, cx);
25462    });
25463    cx.assert_editor_state(indoc! {"
25464        def main():
25465            try:
25466                i = 2
25467            except:
25468                j = 2
25469            else:ˇ
25470    "});
25471
25472    // test `finally` auto outdents when typed inside `else` block
25473    cx.set_state(indoc! {"
25474        def main():
25475            try:
25476                i = 2
25477            except:
25478                j = 2
25479            else:
25480                k = 2
25481                ˇ
25482    "});
25483    cx.update_editor(|editor, window, cx| {
25484        editor.handle_input("finally:", window, cx);
25485    });
25486    cx.assert_editor_state(indoc! {"
25487        def main():
25488            try:
25489                i = 2
25490            except:
25491                j = 2
25492            else:
25493                k = 2
25494            finally:ˇ
25495    "});
25496
25497    // test `else` does not outdents when typed inside `except` block right after for block
25498    cx.set_state(indoc! {"
25499        def main():
25500            try:
25501                i = 2
25502            except:
25503                for i in range(n):
25504                    pass
25505                ˇ
25506    "});
25507    cx.update_editor(|editor, window, cx| {
25508        editor.handle_input("else:", window, cx);
25509    });
25510    cx.assert_editor_state(indoc! {"
25511        def main():
25512            try:
25513                i = 2
25514            except:
25515                for i in range(n):
25516                    pass
25517                else:ˇ
25518    "});
25519
25520    // test `finally` auto outdents when typed inside `else` block right after for block
25521    cx.set_state(indoc! {"
25522        def main():
25523            try:
25524                i = 2
25525            except:
25526                j = 2
25527            else:
25528                for i in range(n):
25529                    pass
25530                ˇ
25531    "});
25532    cx.update_editor(|editor, window, cx| {
25533        editor.handle_input("finally:", window, cx);
25534    });
25535    cx.assert_editor_state(indoc! {"
25536        def main():
25537            try:
25538                i = 2
25539            except:
25540                j = 2
25541            else:
25542                for i in range(n):
25543                    pass
25544            finally:ˇ
25545    "});
25546
25547    // test `except` outdents to inner "try" block
25548    cx.set_state(indoc! {"
25549        def main():
25550            try:
25551                i = 2
25552                if i == 2:
25553                    try:
25554                        i = 3
25555                        ˇ
25556    "});
25557    cx.update_editor(|editor, window, cx| {
25558        editor.handle_input("except:", window, cx);
25559    });
25560    cx.assert_editor_state(indoc! {"
25561        def main():
25562            try:
25563                i = 2
25564                if i == 2:
25565                    try:
25566                        i = 3
25567                    except:ˇ
25568    "});
25569
25570    // test `except` outdents to outer "try" block
25571    cx.set_state(indoc! {"
25572        def main():
25573            try:
25574                i = 2
25575                if i == 2:
25576                    try:
25577                        i = 3
25578                ˇ
25579    "});
25580    cx.update_editor(|editor, window, cx| {
25581        editor.handle_input("except:", window, cx);
25582    });
25583    cx.assert_editor_state(indoc! {"
25584        def main():
25585            try:
25586                i = 2
25587                if i == 2:
25588                    try:
25589                        i = 3
25590            except:ˇ
25591    "});
25592
25593    // test `else` stays at correct indent when typed after `for` block
25594    cx.set_state(indoc! {"
25595        def main():
25596            for i in range(10):
25597                if i == 3:
25598                    break
25599            ˇ
25600    "});
25601    cx.update_editor(|editor, window, cx| {
25602        editor.handle_input("else:", window, cx);
25603    });
25604    cx.assert_editor_state(indoc! {"
25605        def main():
25606            for i in range(10):
25607                if i == 3:
25608                    break
25609            else:ˇ
25610    "});
25611
25612    // test does not outdent on typing after line with square brackets
25613    cx.set_state(indoc! {"
25614        def f() -> list[str]:
25615            ˇ
25616    "});
25617    cx.update_editor(|editor, window, cx| {
25618        editor.handle_input("a", window, cx);
25619    });
25620    cx.assert_editor_state(indoc! {"
25621        def f() -> list[str]:
2562225623    "});
25624
25625    // test does not outdent on typing : after case keyword
25626    cx.set_state(indoc! {"
25627        match 1:
25628            caseˇ
25629    "});
25630    cx.update_editor(|editor, window, cx| {
25631        editor.handle_input(":", window, cx);
25632    });
25633    cx.assert_editor_state(indoc! {"
25634        match 1:
25635            case:ˇ
25636    "});
25637}
25638
25639#[gpui::test]
25640async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25641    init_test(cx, |_| {});
25642    update_test_language_settings(cx, |settings| {
25643        settings.defaults.extend_comment_on_newline = Some(false);
25644    });
25645    let mut cx = EditorTestContext::new(cx).await;
25646    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25647    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25648
25649    // test correct indent after newline on comment
25650    cx.set_state(indoc! {"
25651        # COMMENT:ˇ
25652    "});
25653    cx.update_editor(|editor, window, cx| {
25654        editor.newline(&Newline, window, cx);
25655    });
25656    cx.assert_editor_state(indoc! {"
25657        # COMMENT:
25658        ˇ
25659    "});
25660
25661    // test correct indent after newline in brackets
25662    cx.set_state(indoc! {"
25663        {ˇ}
25664    "});
25665    cx.update_editor(|editor, window, cx| {
25666        editor.newline(&Newline, window, cx);
25667    });
25668    cx.run_until_parked();
25669    cx.assert_editor_state(indoc! {"
25670        {
25671            ˇ
25672        }
25673    "});
25674
25675    cx.set_state(indoc! {"
25676        (ˇ)
25677    "});
25678    cx.update_editor(|editor, window, cx| {
25679        editor.newline(&Newline, window, cx);
25680    });
25681    cx.run_until_parked();
25682    cx.assert_editor_state(indoc! {"
25683        (
25684            ˇ
25685        )
25686    "});
25687
25688    // do not indent after empty lists or dictionaries
25689    cx.set_state(indoc! {"
25690        a = []ˇ
25691    "});
25692    cx.update_editor(|editor, window, cx| {
25693        editor.newline(&Newline, window, cx);
25694    });
25695    cx.run_until_parked();
25696    cx.assert_editor_state(indoc! {"
25697        a = []
25698        ˇ
25699    "});
25700}
25701
25702#[gpui::test]
25703async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25704    init_test(cx, |_| {});
25705
25706    let mut cx = EditorTestContext::new(cx).await;
25707    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25708    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25709
25710    // test cursor move to start of each line on tab
25711    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25712    cx.set_state(indoc! {"
25713        function main() {
25714        ˇ    for item in $items; do
25715        ˇ        while [ -n \"$item\" ]; do
25716        ˇ            if [ \"$value\" -gt 10 ]; then
25717        ˇ                continue
25718        ˇ            elif [ \"$value\" -lt 0 ]; then
25719        ˇ                break
25720        ˇ            else
25721        ˇ                echo \"$item\"
25722        ˇ            fi
25723        ˇ        done
25724        ˇ    done
25725        ˇ}
25726    "});
25727    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25728    cx.assert_editor_state(indoc! {"
25729        function main() {
25730            ˇfor item in $items; do
25731                ˇwhile [ -n \"$item\" ]; do
25732                    ˇif [ \"$value\" -gt 10 ]; then
25733                        ˇcontinue
25734                    ˇelif [ \"$value\" -lt 0 ]; then
25735                        ˇbreak
25736                    ˇelse
25737                        ˇecho \"$item\"
25738                    ˇfi
25739                ˇdone
25740            ˇdone
25741        ˇ}
25742    "});
25743    // test relative indent is preserved when tab
25744    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25745    cx.assert_editor_state(indoc! {"
25746        function main() {
25747                ˇfor item in $items; do
25748                    ˇwhile [ -n \"$item\" ]; do
25749                        ˇif [ \"$value\" -gt 10 ]; then
25750                            ˇcontinue
25751                        ˇelif [ \"$value\" -lt 0 ]; then
25752                            ˇbreak
25753                        ˇelse
25754                            ˇecho \"$item\"
25755                        ˇfi
25756                    ˇdone
25757                ˇdone
25758            ˇ}
25759    "});
25760
25761    // test cursor move to start of each line on tab
25762    // for `case` statement with patterns
25763    cx.set_state(indoc! {"
25764        function handle() {
25765        ˇ    case \"$1\" in
25766        ˇ        start)
25767        ˇ            echo \"a\"
25768        ˇ            ;;
25769        ˇ        stop)
25770        ˇ            echo \"b\"
25771        ˇ            ;;
25772        ˇ        *)
25773        ˇ            echo \"c\"
25774        ˇ            ;;
25775        ˇ    esac
25776        ˇ}
25777    "});
25778    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25779    cx.assert_editor_state(indoc! {"
25780        function handle() {
25781            ˇcase \"$1\" in
25782                ˇstart)
25783                    ˇecho \"a\"
25784                    ˇ;;
25785                ˇstop)
25786                    ˇecho \"b\"
25787                    ˇ;;
25788                ˇ*)
25789                    ˇecho \"c\"
25790                    ˇ;;
25791            ˇesac
25792        ˇ}
25793    "});
25794}
25795
25796#[gpui::test]
25797async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25798    init_test(cx, |_| {});
25799
25800    let mut cx = EditorTestContext::new(cx).await;
25801    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25802    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25803
25804    // test indents on comment insert
25805    cx.set_state(indoc! {"
25806        function main() {
25807        ˇ    for item in $items; do
25808        ˇ        while [ -n \"$item\" ]; do
25809        ˇ            if [ \"$value\" -gt 10 ]; then
25810        ˇ                continue
25811        ˇ            elif [ \"$value\" -lt 0 ]; then
25812        ˇ                break
25813        ˇ            else
25814        ˇ                echo \"$item\"
25815        ˇ            fi
25816        ˇ        done
25817        ˇ    done
25818        ˇ}
25819    "});
25820    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25821    cx.assert_editor_state(indoc! {"
25822        function main() {
25823        #ˇ    for item in $items; do
25824        #ˇ        while [ -n \"$item\" ]; do
25825        #ˇ            if [ \"$value\" -gt 10 ]; then
25826        #ˇ                continue
25827        #ˇ            elif [ \"$value\" -lt 0 ]; then
25828        #ˇ                break
25829        #ˇ            else
25830        #ˇ                echo \"$item\"
25831        #ˇ            fi
25832        #ˇ        done
25833        #ˇ    done
25834        #ˇ}
25835    "});
25836}
25837
25838#[gpui::test]
25839async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25840    init_test(cx, |_| {});
25841
25842    let mut cx = EditorTestContext::new(cx).await;
25843    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25844    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25845
25846    // test `else` auto outdents when typed inside `if` block
25847    cx.set_state(indoc! {"
25848        if [ \"$1\" = \"test\" ]; then
25849            echo \"foo bar\"
25850            ˇ
25851    "});
25852    cx.update_editor(|editor, window, cx| {
25853        editor.handle_input("else", window, cx);
25854    });
25855    cx.assert_editor_state(indoc! {"
25856        if [ \"$1\" = \"test\" ]; then
25857            echo \"foo bar\"
25858        elseˇ
25859    "});
25860
25861    // test `elif` auto outdents when typed inside `if` block
25862    cx.set_state(indoc! {"
25863        if [ \"$1\" = \"test\" ]; then
25864            echo \"foo bar\"
25865            ˇ
25866    "});
25867    cx.update_editor(|editor, window, cx| {
25868        editor.handle_input("elif", window, cx);
25869    });
25870    cx.assert_editor_state(indoc! {"
25871        if [ \"$1\" = \"test\" ]; then
25872            echo \"foo bar\"
25873        elifˇ
25874    "});
25875
25876    // test `fi` auto outdents when typed inside `else` block
25877    cx.set_state(indoc! {"
25878        if [ \"$1\" = \"test\" ]; then
25879            echo \"foo bar\"
25880        else
25881            echo \"bar baz\"
25882            ˇ
25883    "});
25884    cx.update_editor(|editor, window, cx| {
25885        editor.handle_input("fi", window, cx);
25886    });
25887    cx.assert_editor_state(indoc! {"
25888        if [ \"$1\" = \"test\" ]; then
25889            echo \"foo bar\"
25890        else
25891            echo \"bar baz\"
25892        fiˇ
25893    "});
25894
25895    // test `done` auto outdents when typed inside `while` block
25896    cx.set_state(indoc! {"
25897        while read line; do
25898            echo \"$line\"
25899            ˇ
25900    "});
25901    cx.update_editor(|editor, window, cx| {
25902        editor.handle_input("done", window, cx);
25903    });
25904    cx.assert_editor_state(indoc! {"
25905        while read line; do
25906            echo \"$line\"
25907        doneˇ
25908    "});
25909
25910    // test `done` auto outdents when typed inside `for` block
25911    cx.set_state(indoc! {"
25912        for file in *.txt; do
25913            cat \"$file\"
25914            ˇ
25915    "});
25916    cx.update_editor(|editor, window, cx| {
25917        editor.handle_input("done", window, cx);
25918    });
25919    cx.assert_editor_state(indoc! {"
25920        for file in *.txt; do
25921            cat \"$file\"
25922        doneˇ
25923    "});
25924
25925    // test `esac` auto outdents when typed inside `case` block
25926    cx.set_state(indoc! {"
25927        case \"$1\" in
25928            start)
25929                echo \"foo bar\"
25930                ;;
25931            stop)
25932                echo \"bar baz\"
25933                ;;
25934            ˇ
25935    "});
25936    cx.update_editor(|editor, window, cx| {
25937        editor.handle_input("esac", window, cx);
25938    });
25939    cx.assert_editor_state(indoc! {"
25940        case \"$1\" in
25941            start)
25942                echo \"foo bar\"
25943                ;;
25944            stop)
25945                echo \"bar baz\"
25946                ;;
25947        esacˇ
25948    "});
25949
25950    // test `*)` auto outdents when typed inside `case` block
25951    cx.set_state(indoc! {"
25952        case \"$1\" in
25953            start)
25954                echo \"foo bar\"
25955                ;;
25956                ˇ
25957    "});
25958    cx.update_editor(|editor, window, cx| {
25959        editor.handle_input("*)", window, cx);
25960    });
25961    cx.assert_editor_state(indoc! {"
25962        case \"$1\" in
25963            start)
25964                echo \"foo bar\"
25965                ;;
25966            *)ˇ
25967    "});
25968
25969    // test `fi` outdents to correct level with nested if blocks
25970    cx.set_state(indoc! {"
25971        if [ \"$1\" = \"test\" ]; then
25972            echo \"outer if\"
25973            if [ \"$2\" = \"debug\" ]; then
25974                echo \"inner if\"
25975                ˇ
25976    "});
25977    cx.update_editor(|editor, window, cx| {
25978        editor.handle_input("fi", window, cx);
25979    });
25980    cx.assert_editor_state(indoc! {"
25981        if [ \"$1\" = \"test\" ]; then
25982            echo \"outer if\"
25983            if [ \"$2\" = \"debug\" ]; then
25984                echo \"inner if\"
25985            fiˇ
25986    "});
25987}
25988
25989#[gpui::test]
25990async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25991    init_test(cx, |_| {});
25992    update_test_language_settings(cx, |settings| {
25993        settings.defaults.extend_comment_on_newline = Some(false);
25994    });
25995    let mut cx = EditorTestContext::new(cx).await;
25996    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25997    cx.update_buffer(|buffer, cx| buffer.set_language(Some(language), cx));
25998
25999    // test correct indent after newline on comment
26000    cx.set_state(indoc! {"
26001        # COMMENT:ˇ
26002    "});
26003    cx.update_editor(|editor, window, cx| {
26004        editor.newline(&Newline, window, cx);
26005    });
26006    cx.assert_editor_state(indoc! {"
26007        # COMMENT:
26008        ˇ
26009    "});
26010
26011    // test correct indent after newline after `then`
26012    cx.set_state(indoc! {"
26013
26014        if [ \"$1\" = \"test\" ]; thenˇ
26015    "});
26016    cx.update_editor(|editor, window, cx| {
26017        editor.newline(&Newline, window, cx);
26018    });
26019    cx.run_until_parked();
26020    cx.assert_editor_state(indoc! {"
26021
26022        if [ \"$1\" = \"test\" ]; then
26023            ˇ
26024    "});
26025
26026    // test correct indent after newline after `else`
26027    cx.set_state(indoc! {"
26028        if [ \"$1\" = \"test\" ]; then
26029        elseˇ
26030    "});
26031    cx.update_editor(|editor, window, cx| {
26032        editor.newline(&Newline, window, cx);
26033    });
26034    cx.run_until_parked();
26035    cx.assert_editor_state(indoc! {"
26036        if [ \"$1\" = \"test\" ]; then
26037        else
26038            ˇ
26039    "});
26040
26041    // test correct indent after newline after `elif`
26042    cx.set_state(indoc! {"
26043        if [ \"$1\" = \"test\" ]; then
26044        elifˇ
26045    "});
26046    cx.update_editor(|editor, window, cx| {
26047        editor.newline(&Newline, window, cx);
26048    });
26049    cx.run_until_parked();
26050    cx.assert_editor_state(indoc! {"
26051        if [ \"$1\" = \"test\" ]; then
26052        elif
26053            ˇ
26054    "});
26055
26056    // test correct indent after newline after `do`
26057    cx.set_state(indoc! {"
26058        for file in *.txt; doˇ
26059    "});
26060    cx.update_editor(|editor, window, cx| {
26061        editor.newline(&Newline, window, cx);
26062    });
26063    cx.run_until_parked();
26064    cx.assert_editor_state(indoc! {"
26065        for file in *.txt; do
26066            ˇ
26067    "});
26068
26069    // test correct indent after newline after case pattern
26070    cx.set_state(indoc! {"
26071        case \"$1\" in
26072            start)ˇ
26073    "});
26074    cx.update_editor(|editor, window, cx| {
26075        editor.newline(&Newline, window, cx);
26076    });
26077    cx.run_until_parked();
26078    cx.assert_editor_state(indoc! {"
26079        case \"$1\" in
26080            start)
26081                ˇ
26082    "});
26083
26084    // test correct indent after newline after case pattern
26085    cx.set_state(indoc! {"
26086        case \"$1\" in
26087            start)
26088                ;;
26089            *)ˇ
26090    "});
26091    cx.update_editor(|editor, window, cx| {
26092        editor.newline(&Newline, window, cx);
26093    });
26094    cx.run_until_parked();
26095    cx.assert_editor_state(indoc! {"
26096        case \"$1\" in
26097            start)
26098                ;;
26099            *)
26100                ˇ
26101    "});
26102
26103    // test correct indent after newline after function opening brace
26104    cx.set_state(indoc! {"
26105        function test() {ˇ}
26106    "});
26107    cx.update_editor(|editor, window, cx| {
26108        editor.newline(&Newline, window, cx);
26109    });
26110    cx.run_until_parked();
26111    cx.assert_editor_state(indoc! {"
26112        function test() {
26113            ˇ
26114        }
26115    "});
26116
26117    // test no extra indent after semicolon on same line
26118    cx.set_state(indoc! {"
26119        echo \"test\"26120    "});
26121    cx.update_editor(|editor, window, cx| {
26122        editor.newline(&Newline, window, cx);
26123    });
26124    cx.run_until_parked();
26125    cx.assert_editor_state(indoc! {"
26126        echo \"test\";
26127        ˇ
26128    "});
26129}
26130
26131fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
26132    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
26133    point..point
26134}
26135
26136#[track_caller]
26137fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
26138    let (text, ranges) = marked_text_ranges(marked_text, true);
26139    assert_eq!(editor.text(cx), text);
26140    assert_eq!(
26141        editor.selections.ranges(&editor.display_snapshot(cx)),
26142        ranges
26143            .iter()
26144            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
26145            .collect::<Vec<_>>(),
26146        "Assert selections are {}",
26147        marked_text
26148    );
26149}
26150
26151pub fn handle_signature_help_request(
26152    cx: &mut EditorLspTestContext,
26153    mocked_response: lsp::SignatureHelp,
26154) -> impl Future<Output = ()> + use<> {
26155    let mut request =
26156        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
26157            let mocked_response = mocked_response.clone();
26158            async move { Ok(Some(mocked_response)) }
26159        });
26160
26161    async move {
26162        request.next().await;
26163    }
26164}
26165
26166#[track_caller]
26167pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
26168    cx.update_editor(|editor, _, _| {
26169        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
26170            let entries = menu.entries.borrow();
26171            let entries = entries
26172                .iter()
26173                .map(|entry| entry.string.as_str())
26174                .collect::<Vec<_>>();
26175            assert_eq!(entries, expected);
26176        } else {
26177            panic!("Expected completions menu");
26178        }
26179    });
26180}
26181
26182#[gpui::test]
26183async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
26184    init_test(cx, |_| {});
26185    let mut cx = EditorLspTestContext::new_rust(
26186        lsp::ServerCapabilities {
26187            completion_provider: Some(lsp::CompletionOptions {
26188                ..Default::default()
26189            }),
26190            ..Default::default()
26191        },
26192        cx,
26193    )
26194    .await;
26195    cx.lsp
26196        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
26197            Ok(Some(lsp::CompletionResponse::Array(vec![
26198                lsp::CompletionItem {
26199                    label: "unsafe".into(),
26200                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26201                        range: lsp::Range {
26202                            start: lsp::Position {
26203                                line: 0,
26204                                character: 9,
26205                            },
26206                            end: lsp::Position {
26207                                line: 0,
26208                                character: 11,
26209                            },
26210                        },
26211                        new_text: "unsafe".to_string(),
26212                    })),
26213                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
26214                    ..Default::default()
26215                },
26216            ])))
26217        });
26218
26219    cx.update_editor(|editor, _, cx| {
26220        editor.project().unwrap().update(cx, |project, cx| {
26221            project.snippets().update(cx, |snippets, _cx| {
26222                snippets.add_snippet_for_test(
26223                    None,
26224                    PathBuf::from("test_snippets.json"),
26225                    vec![
26226                        Arc::new(project::snippet_provider::Snippet {
26227                            prefix: vec![
26228                                "unlimited word count".to_string(),
26229                                "unlimit word count".to_string(),
26230                                "unlimited unknown".to_string(),
26231                            ],
26232                            body: "this is many words".to_string(),
26233                            description: Some("description".to_string()),
26234                            name: "multi-word snippet test".to_string(),
26235                        }),
26236                        Arc::new(project::snippet_provider::Snippet {
26237                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
26238                            body: "fewer words".to_string(),
26239                            description: Some("alt description".to_string()),
26240                            name: "other name".to_string(),
26241                        }),
26242                        Arc::new(project::snippet_provider::Snippet {
26243                            prefix: vec!["ab aa".to_string()],
26244                            body: "abcd".to_string(),
26245                            description: None,
26246                            name: "alphabet".to_string(),
26247                        }),
26248                    ],
26249                );
26250            });
26251        })
26252    });
26253
26254    let get_completions = |cx: &mut EditorLspTestContext| {
26255        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
26256            Some(CodeContextMenu::Completions(context_menu)) => {
26257                let entries = context_menu.entries.borrow();
26258                entries
26259                    .iter()
26260                    .map(|entry| entry.string.clone())
26261                    .collect_vec()
26262            }
26263            _ => vec![],
26264        })
26265    };
26266
26267    // snippets:
26268    //  @foo
26269    //  foo bar
26270    //
26271    // when typing:
26272    //
26273    // when typing:
26274    //  - if I type a symbol "open the completions with snippets only"
26275    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26276    //
26277    // stuff we need:
26278    //  - filtering logic change?
26279    //  - remember how far back the completion started.
26280
26281    let test_cases: &[(&str, &[&str])] = &[
26282        (
26283            "un",
26284            &[
26285                "unsafe",
26286                "unlimit word count",
26287                "unlimited unknown",
26288                "unlimited word count",
26289                "unsnip",
26290            ],
26291        ),
26292        (
26293            "u ",
26294            &[
26295                "unlimit word count",
26296                "unlimited unknown",
26297                "unlimited word count",
26298            ],
26299        ),
26300        ("u a", &["ab aa", "unsafe"]), // unsAfe
26301        (
26302            "u u",
26303            &[
26304                "unsafe",
26305                "unlimit word count",
26306                "unlimited unknown", // ranked highest among snippets
26307                "unlimited word count",
26308                "unsnip",
26309            ],
26310        ),
26311        ("uw c", &["unlimit word count", "unlimited word count"]),
26312        (
26313            "u w",
26314            &[
26315                "unlimit word count",
26316                "unlimited word count",
26317                "unlimited unknown",
26318            ],
26319        ),
26320        ("u w ", &["unlimit word count", "unlimited word count"]),
26321        (
26322            "u ",
26323            &[
26324                "unlimit word count",
26325                "unlimited unknown",
26326                "unlimited word count",
26327            ],
26328        ),
26329        ("wor", &[]),
26330        ("uf", &["unsafe"]),
26331        ("af", &["unsafe"]),
26332        ("afu", &[]),
26333        (
26334            "ue",
26335            &["unsafe", "unlimited unknown", "unlimited word count"],
26336        ),
26337        ("@", &["@few"]),
26338        ("@few", &["@few"]),
26339        ("@ ", &[]),
26340        ("a@", &["@few"]),
26341        ("a@f", &["@few", "unsafe"]),
26342        ("a@fw", &["@few"]),
26343        ("a", &["ab aa", "unsafe"]),
26344        ("aa", &["ab aa"]),
26345        ("aaa", &["ab aa"]),
26346        ("ab", &["ab aa"]),
26347        ("ab ", &["ab aa"]),
26348        ("ab a", &["ab aa", "unsafe"]),
26349        ("ab ab", &["ab aa"]),
26350        ("ab ab aa", &["ab aa"]),
26351    ];
26352
26353    for &(input_to_simulate, expected_completions) in test_cases {
26354        cx.set_state("fn a() { ˇ }\n");
26355        for c in input_to_simulate.split("") {
26356            cx.simulate_input(c);
26357            cx.run_until_parked();
26358        }
26359        let expected_completions = expected_completions
26360            .iter()
26361            .map(|s| s.to_string())
26362            .collect_vec();
26363        assert_eq!(
26364            get_completions(&mut cx),
26365            expected_completions,
26366            "< actual / expected >, input = {input_to_simulate:?}",
26367        );
26368    }
26369}
26370
26371/// Handle completion request passing a marked string specifying where the completion
26372/// should be triggered from using '|' character, what range should be replaced, and what completions
26373/// should be returned using '<' and '>' to delimit the range.
26374///
26375/// Also see `handle_completion_request_with_insert_and_replace`.
26376#[track_caller]
26377pub fn handle_completion_request(
26378    marked_string: &str,
26379    completions: Vec<&'static str>,
26380    is_incomplete: bool,
26381    counter: Arc<AtomicUsize>,
26382    cx: &mut EditorLspTestContext,
26383) -> impl Future<Output = ()> {
26384    let complete_from_marker: TextRangeMarker = '|'.into();
26385    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26386    let (_, mut marked_ranges) = marked_text_ranges_by(
26387        marked_string,
26388        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26389    );
26390
26391    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26392        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26393    ));
26394    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26395    let replace_range =
26396        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26397
26398    let mut request =
26399        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26400            let completions = completions.clone();
26401            counter.fetch_add(1, atomic::Ordering::Release);
26402            async move {
26403                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26404                assert_eq!(
26405                    params.text_document_position.position,
26406                    complete_from_position
26407                );
26408                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26409                    is_incomplete,
26410                    item_defaults: None,
26411                    items: completions
26412                        .iter()
26413                        .map(|completion_text| lsp::CompletionItem {
26414                            label: completion_text.to_string(),
26415                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26416                                range: replace_range,
26417                                new_text: completion_text.to_string(),
26418                            })),
26419                            ..Default::default()
26420                        })
26421                        .collect(),
26422                })))
26423            }
26424        });
26425
26426    async move {
26427        request.next().await;
26428    }
26429}
26430
26431/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26432/// given instead, which also contains an `insert` range.
26433///
26434/// This function uses markers to define ranges:
26435/// - `|` marks the cursor position
26436/// - `<>` marks the replace range
26437/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26438pub fn handle_completion_request_with_insert_and_replace(
26439    cx: &mut EditorLspTestContext,
26440    marked_string: &str,
26441    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26442    counter: Arc<AtomicUsize>,
26443) -> impl Future<Output = ()> {
26444    let complete_from_marker: TextRangeMarker = '|'.into();
26445    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26446    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26447
26448    let (_, mut marked_ranges) = marked_text_ranges_by(
26449        marked_string,
26450        vec![
26451            complete_from_marker.clone(),
26452            replace_range_marker.clone(),
26453            insert_range_marker.clone(),
26454        ],
26455    );
26456
26457    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26458        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26459    ));
26460    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26461    let replace_range =
26462        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26463
26464    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26465        Some(ranges) if !ranges.is_empty() => {
26466            let range1 = ranges[0].clone();
26467            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26468        }
26469        _ => lsp::Range {
26470            start: replace_range.start,
26471            end: complete_from_position,
26472        },
26473    };
26474
26475    let mut request =
26476        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26477            let completions = completions.clone();
26478            counter.fetch_add(1, atomic::Ordering::Release);
26479            async move {
26480                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26481                assert_eq!(
26482                    params.text_document_position.position, complete_from_position,
26483                    "marker `|` position doesn't match",
26484                );
26485                Ok(Some(lsp::CompletionResponse::Array(
26486                    completions
26487                        .iter()
26488                        .map(|(label, new_text)| lsp::CompletionItem {
26489                            label: label.to_string(),
26490                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26491                                lsp::InsertReplaceEdit {
26492                                    insert: insert_range,
26493                                    replace: replace_range,
26494                                    new_text: new_text.to_string(),
26495                                },
26496                            )),
26497                            ..Default::default()
26498                        })
26499                        .collect(),
26500                )))
26501            }
26502        });
26503
26504    async move {
26505        request.next().await;
26506    }
26507}
26508
26509fn handle_resolve_completion_request(
26510    cx: &mut EditorLspTestContext,
26511    edits: Option<Vec<(&'static str, &'static str)>>,
26512) -> impl Future<Output = ()> {
26513    let edits = edits.map(|edits| {
26514        edits
26515            .iter()
26516            .map(|(marked_string, new_text)| {
26517                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26518                let replace_range = cx.to_lsp_range(
26519                    MultiBufferOffset(marked_ranges[0].start)
26520                        ..MultiBufferOffset(marked_ranges[0].end),
26521                );
26522                lsp::TextEdit::new(replace_range, new_text.to_string())
26523            })
26524            .collect::<Vec<_>>()
26525    });
26526
26527    let mut request =
26528        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26529            let edits = edits.clone();
26530            async move {
26531                Ok(lsp::CompletionItem {
26532                    additional_text_edits: edits,
26533                    ..Default::default()
26534                })
26535            }
26536        });
26537
26538    async move {
26539        request.next().await;
26540    }
26541}
26542
26543pub(crate) fn update_test_language_settings(
26544    cx: &mut TestAppContext,
26545    f: impl Fn(&mut AllLanguageSettingsContent),
26546) {
26547    cx.update(|cx| {
26548        SettingsStore::update_global(cx, |store, cx| {
26549            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26550        });
26551    });
26552}
26553
26554pub(crate) fn update_test_project_settings(
26555    cx: &mut TestAppContext,
26556    f: impl Fn(&mut ProjectSettingsContent),
26557) {
26558    cx.update(|cx| {
26559        SettingsStore::update_global(cx, |store, cx| {
26560            store.update_user_settings(cx, |settings| f(&mut settings.project));
26561        });
26562    });
26563}
26564
26565pub(crate) fn update_test_editor_settings(
26566    cx: &mut TestAppContext,
26567    f: impl Fn(&mut EditorSettingsContent),
26568) {
26569    cx.update(|cx| {
26570        SettingsStore::update_global(cx, |store, cx| {
26571            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26572        })
26573    })
26574}
26575
26576pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26577    cx.update(|cx| {
26578        assets::Assets.load_test_fonts(cx);
26579        let store = SettingsStore::test(cx);
26580        cx.set_global(store);
26581        theme::init(theme::LoadThemes::JustBase, cx);
26582        release_channel::init(semver::Version::new(0, 0, 0), cx);
26583        crate::init(cx);
26584    });
26585    zlog::init_test();
26586    update_test_language_settings(cx, f);
26587}
26588
26589#[track_caller]
26590fn assert_hunk_revert(
26591    not_reverted_text_with_selections: &str,
26592    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26593    expected_reverted_text_with_selections: &str,
26594    base_text: &str,
26595    cx: &mut EditorLspTestContext,
26596) {
26597    cx.set_state(not_reverted_text_with_selections);
26598    cx.set_head_text(base_text);
26599    cx.executor().run_until_parked();
26600
26601    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26602        let snapshot = editor.snapshot(window, cx);
26603        let reverted_hunk_statuses = snapshot
26604            .buffer_snapshot()
26605            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26606            .map(|hunk| hunk.status().kind)
26607            .collect::<Vec<_>>();
26608
26609        editor.git_restore(&Default::default(), window, cx);
26610        reverted_hunk_statuses
26611    });
26612    cx.executor().run_until_parked();
26613    cx.assert_editor_state(expected_reverted_text_with_selections);
26614    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26615}
26616
26617#[gpui::test(iterations = 10)]
26618async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26619    init_test(cx, |_| {});
26620
26621    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26622    let counter = diagnostic_requests.clone();
26623
26624    let fs = FakeFs::new(cx.executor());
26625    fs.insert_tree(
26626        path!("/a"),
26627        json!({
26628            "first.rs": "fn main() { let a = 5; }",
26629            "second.rs": "// Test file",
26630        }),
26631    )
26632    .await;
26633
26634    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26635    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26636    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26637
26638    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26639    language_registry.add(rust_lang());
26640    let mut fake_servers = language_registry.register_fake_lsp(
26641        "Rust",
26642        FakeLspAdapter {
26643            capabilities: lsp::ServerCapabilities {
26644                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26645                    lsp::DiagnosticOptions {
26646                        identifier: None,
26647                        inter_file_dependencies: true,
26648                        workspace_diagnostics: true,
26649                        work_done_progress_options: Default::default(),
26650                    },
26651                )),
26652                ..Default::default()
26653            },
26654            ..Default::default()
26655        },
26656    );
26657
26658    let editor = workspace
26659        .update(cx, |workspace, window, cx| {
26660            workspace.open_abs_path(
26661                PathBuf::from(path!("/a/first.rs")),
26662                OpenOptions::default(),
26663                window,
26664                cx,
26665            )
26666        })
26667        .unwrap()
26668        .await
26669        .unwrap()
26670        .downcast::<Editor>()
26671        .unwrap();
26672    let fake_server = fake_servers.next().await.unwrap();
26673    let server_id = fake_server.server.server_id();
26674    let mut first_request = fake_server
26675        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26676            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26677            let result_id = Some(new_result_id.to_string());
26678            assert_eq!(
26679                params.text_document.uri,
26680                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26681            );
26682            async move {
26683                Ok(lsp::DocumentDiagnosticReportResult::Report(
26684                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26685                        related_documents: None,
26686                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26687                            items: Vec::new(),
26688                            result_id,
26689                        },
26690                    }),
26691                ))
26692            }
26693        });
26694
26695    let ensure_result_id = |expected: Option<SharedString>, cx: &mut TestAppContext| {
26696        project.update(cx, |project, cx| {
26697            let buffer_id = editor
26698                .read(cx)
26699                .buffer()
26700                .read(cx)
26701                .as_singleton()
26702                .expect("created a singleton buffer")
26703                .read(cx)
26704                .remote_id();
26705            let buffer_result_id = project
26706                .lsp_store()
26707                .read(cx)
26708                .result_id_for_buffer_pull(server_id, buffer_id, &None, cx);
26709            assert_eq!(expected, buffer_result_id);
26710        });
26711    };
26712
26713    ensure_result_id(None, cx);
26714    cx.executor().advance_clock(Duration::from_millis(60));
26715    cx.executor().run_until_parked();
26716    assert_eq!(
26717        diagnostic_requests.load(atomic::Ordering::Acquire),
26718        1,
26719        "Opening file should trigger diagnostic request"
26720    );
26721    first_request
26722        .next()
26723        .await
26724        .expect("should have sent the first diagnostics pull request");
26725    ensure_result_id(Some(SharedString::new("1")), cx);
26726
26727    // Editing should trigger diagnostics
26728    editor.update_in(cx, |editor, window, cx| {
26729        editor.handle_input("2", window, cx)
26730    });
26731    cx.executor().advance_clock(Duration::from_millis(60));
26732    cx.executor().run_until_parked();
26733    assert_eq!(
26734        diagnostic_requests.load(atomic::Ordering::Acquire),
26735        2,
26736        "Editing should trigger diagnostic request"
26737    );
26738    ensure_result_id(Some(SharedString::new("2")), cx);
26739
26740    // Moving cursor should not trigger diagnostic request
26741    editor.update_in(cx, |editor, window, cx| {
26742        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26743            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26744        });
26745    });
26746    cx.executor().advance_clock(Duration::from_millis(60));
26747    cx.executor().run_until_parked();
26748    assert_eq!(
26749        diagnostic_requests.load(atomic::Ordering::Acquire),
26750        2,
26751        "Cursor movement should not trigger diagnostic request"
26752    );
26753    ensure_result_id(Some(SharedString::new("2")), cx);
26754    // Multiple rapid edits should be debounced
26755    for _ in 0..5 {
26756        editor.update_in(cx, |editor, window, cx| {
26757            editor.handle_input("x", window, cx)
26758        });
26759    }
26760    cx.executor().advance_clock(Duration::from_millis(60));
26761    cx.executor().run_until_parked();
26762
26763    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26764    assert!(
26765        final_requests <= 4,
26766        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26767    );
26768    ensure_result_id(Some(SharedString::new(final_requests.to_string())), cx);
26769}
26770
26771#[gpui::test]
26772async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26773    // Regression test for issue #11671
26774    // Previously, adding a cursor after moving multiple cursors would reset
26775    // the cursor count instead of adding to the existing cursors.
26776    init_test(cx, |_| {});
26777    let mut cx = EditorTestContext::new(cx).await;
26778
26779    // Create a simple buffer with cursor at start
26780    cx.set_state(indoc! {"
26781        ˇaaaa
26782        bbbb
26783        cccc
26784        dddd
26785        eeee
26786        ffff
26787        gggg
26788        hhhh"});
26789
26790    // Add 2 cursors below (so we have 3 total)
26791    cx.update_editor(|editor, window, cx| {
26792        editor.add_selection_below(&Default::default(), window, cx);
26793        editor.add_selection_below(&Default::default(), window, cx);
26794    });
26795
26796    // Verify we have 3 cursors
26797    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26798    assert_eq!(
26799        initial_count, 3,
26800        "Should have 3 cursors after adding 2 below"
26801    );
26802
26803    // Move down one line
26804    cx.update_editor(|editor, window, cx| {
26805        editor.move_down(&MoveDown, window, cx);
26806    });
26807
26808    // Add another cursor below
26809    cx.update_editor(|editor, window, cx| {
26810        editor.add_selection_below(&Default::default(), window, cx);
26811    });
26812
26813    // Should now have 4 cursors (3 original + 1 new)
26814    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26815    assert_eq!(
26816        final_count, 4,
26817        "Should have 4 cursors after moving and adding another"
26818    );
26819}
26820
26821#[gpui::test]
26822async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26823    init_test(cx, |_| {});
26824
26825    let mut cx = EditorTestContext::new(cx).await;
26826
26827    cx.set_state(indoc!(
26828        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26829           Second line here"#
26830    ));
26831
26832    cx.update_editor(|editor, window, cx| {
26833        // Enable soft wrapping with a narrow width to force soft wrapping and
26834        // confirm that more than 2 rows are being displayed.
26835        editor.set_wrap_width(Some(100.0.into()), cx);
26836        assert!(editor.display_text(cx).lines().count() > 2);
26837
26838        editor.add_selection_below(
26839            &AddSelectionBelow {
26840                skip_soft_wrap: true,
26841            },
26842            window,
26843            cx,
26844        );
26845
26846        assert_eq!(
26847            display_ranges(editor, cx),
26848            &[
26849                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26850                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26851            ]
26852        );
26853
26854        editor.add_selection_above(
26855            &AddSelectionAbove {
26856                skip_soft_wrap: true,
26857            },
26858            window,
26859            cx,
26860        );
26861
26862        assert_eq!(
26863            display_ranges(editor, cx),
26864            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26865        );
26866
26867        editor.add_selection_below(
26868            &AddSelectionBelow {
26869                skip_soft_wrap: false,
26870            },
26871            window,
26872            cx,
26873        );
26874
26875        assert_eq!(
26876            display_ranges(editor, cx),
26877            &[
26878                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26879                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26880            ]
26881        );
26882
26883        editor.add_selection_above(
26884            &AddSelectionAbove {
26885                skip_soft_wrap: false,
26886            },
26887            window,
26888            cx,
26889        );
26890
26891        assert_eq!(
26892            display_ranges(editor, cx),
26893            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26894        );
26895    });
26896}
26897
26898#[gpui::test]
26899async fn test_insert_snippet(cx: &mut TestAppContext) {
26900    init_test(cx, |_| {});
26901    let mut cx = EditorTestContext::new(cx).await;
26902
26903    cx.update_editor(|editor, _, cx| {
26904        editor.project().unwrap().update(cx, |project, cx| {
26905            project.snippets().update(cx, |snippets, _cx| {
26906                let snippet = project::snippet_provider::Snippet {
26907                    prefix: vec![], // no prefix needed!
26908                    body: "an Unspecified".to_string(),
26909                    description: Some("shhhh it's a secret".to_string()),
26910                    name: "super secret snippet".to_string(),
26911                };
26912                snippets.add_snippet_for_test(
26913                    None,
26914                    PathBuf::from("test_snippets.json"),
26915                    vec![Arc::new(snippet)],
26916                );
26917
26918                let snippet = project::snippet_provider::Snippet {
26919                    prefix: vec![], // no prefix needed!
26920                    body: " Location".to_string(),
26921                    description: Some("the word 'location'".to_string()),
26922                    name: "location word".to_string(),
26923                };
26924                snippets.add_snippet_for_test(
26925                    Some("Markdown".to_string()),
26926                    PathBuf::from("test_snippets.json"),
26927                    vec![Arc::new(snippet)],
26928                );
26929            });
26930        })
26931    });
26932
26933    cx.set_state(indoc!(r#"First cursor at ˇ and second cursor at ˇ"#));
26934
26935    cx.update_editor(|editor, window, cx| {
26936        editor.insert_snippet_at_selections(
26937            &InsertSnippet {
26938                language: None,
26939                name: Some("super secret snippet".to_string()),
26940                snippet: None,
26941            },
26942            window,
26943            cx,
26944        );
26945
26946        // Language is specified in the action,
26947        // so the buffer language does not need to match
26948        editor.insert_snippet_at_selections(
26949            &InsertSnippet {
26950                language: Some("Markdown".to_string()),
26951                name: Some("location word".to_string()),
26952                snippet: None,
26953            },
26954            window,
26955            cx,
26956        );
26957
26958        editor.insert_snippet_at_selections(
26959            &InsertSnippet {
26960                language: None,
26961                name: None,
26962                snippet: Some("$0 after".to_string()),
26963            },
26964            window,
26965            cx,
26966        );
26967    });
26968
26969    cx.assert_editor_state(
26970        r#"First cursor at an Unspecified Locationˇ after and second cursor at an Unspecified Locationˇ after"#,
26971    );
26972}
26973
26974#[gpui::test(iterations = 10)]
26975async fn test_document_colors(cx: &mut TestAppContext) {
26976    let expected_color = Rgba {
26977        r: 0.33,
26978        g: 0.33,
26979        b: 0.33,
26980        a: 0.33,
26981    };
26982
26983    init_test(cx, |_| {});
26984
26985    let fs = FakeFs::new(cx.executor());
26986    fs.insert_tree(
26987        path!("/a"),
26988        json!({
26989            "first.rs": "fn main() { let a = 5; }",
26990        }),
26991    )
26992    .await;
26993
26994    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26995    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26996    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26997
26998    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26999    language_registry.add(rust_lang());
27000    let mut fake_servers = language_registry.register_fake_lsp(
27001        "Rust",
27002        FakeLspAdapter {
27003            capabilities: lsp::ServerCapabilities {
27004                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
27005                ..lsp::ServerCapabilities::default()
27006            },
27007            name: "rust-analyzer",
27008            ..FakeLspAdapter::default()
27009        },
27010    );
27011    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
27012        "Rust",
27013        FakeLspAdapter {
27014            capabilities: lsp::ServerCapabilities {
27015                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
27016                ..lsp::ServerCapabilities::default()
27017            },
27018            name: "not-rust-analyzer",
27019            ..FakeLspAdapter::default()
27020        },
27021    );
27022
27023    let editor = workspace
27024        .update(cx, |workspace, window, cx| {
27025            workspace.open_abs_path(
27026                PathBuf::from(path!("/a/first.rs")),
27027                OpenOptions::default(),
27028                window,
27029                cx,
27030            )
27031        })
27032        .unwrap()
27033        .await
27034        .unwrap()
27035        .downcast::<Editor>()
27036        .unwrap();
27037    let fake_language_server = fake_servers.next().await.unwrap();
27038    let fake_language_server_without_capabilities =
27039        fake_servers_without_capabilities.next().await.unwrap();
27040    let requests_made = Arc::new(AtomicUsize::new(0));
27041    let closure_requests_made = Arc::clone(&requests_made);
27042    let mut color_request_handle = fake_language_server
27043        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27044            let requests_made = Arc::clone(&closure_requests_made);
27045            async move {
27046                assert_eq!(
27047                    params.text_document.uri,
27048                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27049                );
27050                requests_made.fetch_add(1, atomic::Ordering::Release);
27051                Ok(vec![
27052                    lsp::ColorInformation {
27053                        range: lsp::Range {
27054                            start: lsp::Position {
27055                                line: 0,
27056                                character: 0,
27057                            },
27058                            end: lsp::Position {
27059                                line: 0,
27060                                character: 1,
27061                            },
27062                        },
27063                        color: lsp::Color {
27064                            red: 0.33,
27065                            green: 0.33,
27066                            blue: 0.33,
27067                            alpha: 0.33,
27068                        },
27069                    },
27070                    lsp::ColorInformation {
27071                        range: lsp::Range {
27072                            start: lsp::Position {
27073                                line: 0,
27074                                character: 0,
27075                            },
27076                            end: lsp::Position {
27077                                line: 0,
27078                                character: 1,
27079                            },
27080                        },
27081                        color: lsp::Color {
27082                            red: 0.33,
27083                            green: 0.33,
27084                            blue: 0.33,
27085                            alpha: 0.33,
27086                        },
27087                    },
27088                ])
27089            }
27090        });
27091
27092    let _handle = fake_language_server_without_capabilities
27093        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
27094            panic!("Should not be called");
27095        });
27096    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27097    color_request_handle.next().await.unwrap();
27098    cx.run_until_parked();
27099    assert_eq!(
27100        1,
27101        requests_made.load(atomic::Ordering::Acquire),
27102        "Should query for colors once per editor open"
27103    );
27104    editor.update_in(cx, |editor, _, cx| {
27105        assert_eq!(
27106            vec![expected_color],
27107            extract_color_inlays(editor, cx),
27108            "Should have an initial inlay"
27109        );
27110    });
27111
27112    // opening another file in a split should not influence the LSP query counter
27113    workspace
27114        .update(cx, |workspace, window, cx| {
27115            assert_eq!(
27116                workspace.panes().len(),
27117                1,
27118                "Should have one pane with one editor"
27119            );
27120            workspace.move_item_to_pane_in_direction(
27121                &MoveItemToPaneInDirection {
27122                    direction: SplitDirection::Right,
27123                    focus: false,
27124                    clone: true,
27125                },
27126                window,
27127                cx,
27128            );
27129        })
27130        .unwrap();
27131    cx.run_until_parked();
27132    workspace
27133        .update(cx, |workspace, _, cx| {
27134            let panes = workspace.panes();
27135            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
27136            for pane in panes {
27137                let editor = pane
27138                    .read(cx)
27139                    .active_item()
27140                    .and_then(|item| item.downcast::<Editor>())
27141                    .expect("Should have opened an editor in each split");
27142                let editor_file = editor
27143                    .read(cx)
27144                    .buffer()
27145                    .read(cx)
27146                    .as_singleton()
27147                    .expect("test deals with singleton buffers")
27148                    .read(cx)
27149                    .file()
27150                    .expect("test buffese should have a file")
27151                    .path();
27152                assert_eq!(
27153                    editor_file.as_ref(),
27154                    rel_path("first.rs"),
27155                    "Both editors should be opened for the same file"
27156                )
27157            }
27158        })
27159        .unwrap();
27160
27161    cx.executor().advance_clock(Duration::from_millis(500));
27162    let save = editor.update_in(cx, |editor, window, cx| {
27163        editor.move_to_end(&MoveToEnd, window, cx);
27164        editor.handle_input("dirty", window, cx);
27165        editor.save(
27166            SaveOptions {
27167                format: true,
27168                autosave: true,
27169            },
27170            project.clone(),
27171            window,
27172            cx,
27173        )
27174    });
27175    save.await.unwrap();
27176
27177    color_request_handle.next().await.unwrap();
27178    cx.run_until_parked();
27179    assert_eq!(
27180        2,
27181        requests_made.load(atomic::Ordering::Acquire),
27182        "Should query for colors once per save (deduplicated) and once per formatting after save"
27183    );
27184
27185    drop(editor);
27186    let close = workspace
27187        .update(cx, |workspace, window, cx| {
27188            workspace.active_pane().update(cx, |pane, cx| {
27189                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27190            })
27191        })
27192        .unwrap();
27193    close.await.unwrap();
27194    let close = workspace
27195        .update(cx, |workspace, window, cx| {
27196            workspace.active_pane().update(cx, |pane, cx| {
27197                pane.close_active_item(&CloseActiveItem::default(), window, cx)
27198            })
27199        })
27200        .unwrap();
27201    close.await.unwrap();
27202    assert_eq!(
27203        2,
27204        requests_made.load(atomic::Ordering::Acquire),
27205        "After saving and closing all editors, no extra requests should be made"
27206    );
27207    workspace
27208        .update(cx, |workspace, _, cx| {
27209            assert!(
27210                workspace.active_item(cx).is_none(),
27211                "Should close all editors"
27212            )
27213        })
27214        .unwrap();
27215
27216    workspace
27217        .update(cx, |workspace, window, cx| {
27218            workspace.active_pane().update(cx, |pane, cx| {
27219                pane.navigate_backward(&workspace::GoBack, window, cx);
27220            })
27221        })
27222        .unwrap();
27223    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27224    cx.run_until_parked();
27225    let editor = workspace
27226        .update(cx, |workspace, _, cx| {
27227            workspace
27228                .active_item(cx)
27229                .expect("Should have reopened the editor again after navigating back")
27230                .downcast::<Editor>()
27231                .expect("Should be an editor")
27232        })
27233        .unwrap();
27234
27235    assert_eq!(
27236        2,
27237        requests_made.load(atomic::Ordering::Acquire),
27238        "Cache should be reused on buffer close and reopen"
27239    );
27240    editor.update(cx, |editor, cx| {
27241        assert_eq!(
27242            vec![expected_color],
27243            extract_color_inlays(editor, cx),
27244            "Should have an initial inlay"
27245        );
27246    });
27247
27248    drop(color_request_handle);
27249    let closure_requests_made = Arc::clone(&requests_made);
27250    let mut empty_color_request_handle = fake_language_server
27251        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
27252            let requests_made = Arc::clone(&closure_requests_made);
27253            async move {
27254                assert_eq!(
27255                    params.text_document.uri,
27256                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
27257                );
27258                requests_made.fetch_add(1, atomic::Ordering::Release);
27259                Ok(Vec::new())
27260            }
27261        });
27262    let save = editor.update_in(cx, |editor, window, cx| {
27263        editor.move_to_end(&MoveToEnd, window, cx);
27264        editor.handle_input("dirty_again", window, cx);
27265        editor.save(
27266            SaveOptions {
27267                format: false,
27268                autosave: true,
27269            },
27270            project.clone(),
27271            window,
27272            cx,
27273        )
27274    });
27275    save.await.unwrap();
27276
27277    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
27278    empty_color_request_handle.next().await.unwrap();
27279    cx.run_until_parked();
27280    assert_eq!(
27281        3,
27282        requests_made.load(atomic::Ordering::Acquire),
27283        "Should query for colors once per save only, as formatting was not requested"
27284    );
27285    editor.update(cx, |editor, cx| {
27286        assert_eq!(
27287            Vec::<Rgba>::new(),
27288            extract_color_inlays(editor, cx),
27289            "Should clear all colors when the server returns an empty response"
27290        );
27291    });
27292}
27293
27294#[gpui::test]
27295async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
27296    init_test(cx, |_| {});
27297    let (editor, cx) = cx.add_window_view(Editor::single_line);
27298    editor.update_in(cx, |editor, window, cx| {
27299        editor.set_text("oops\n\nwow\n", window, cx)
27300    });
27301    cx.run_until_parked();
27302    editor.update(cx, |editor, cx| {
27303        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
27304    });
27305    editor.update(cx, |editor, cx| {
27306        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
27307    });
27308    cx.run_until_parked();
27309    editor.update(cx, |editor, cx| {
27310        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
27311    });
27312}
27313
27314#[gpui::test]
27315async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
27316    init_test(cx, |_| {});
27317
27318    cx.update(|cx| {
27319        register_project_item::<Editor>(cx);
27320    });
27321
27322    let fs = FakeFs::new(cx.executor());
27323    fs.insert_tree("/root1", json!({})).await;
27324    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
27325        .await;
27326
27327    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
27328    let (workspace, cx) =
27329        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
27330
27331    let worktree_id = project.update(cx, |project, cx| {
27332        project.worktrees(cx).next().unwrap().read(cx).id()
27333    });
27334
27335    let handle = workspace
27336        .update_in(cx, |workspace, window, cx| {
27337            let project_path = (worktree_id, rel_path("one.pdf"));
27338            workspace.open_path(project_path, None, true, window, cx)
27339        })
27340        .await
27341        .unwrap();
27342
27343    assert_eq!(
27344        handle.to_any_view().entity_type(),
27345        TypeId::of::<InvalidItemView>()
27346    );
27347}
27348
27349#[gpui::test]
27350async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27351    init_test(cx, |_| {});
27352
27353    let language = Arc::new(Language::new(
27354        LanguageConfig::default(),
27355        Some(tree_sitter_rust::LANGUAGE.into()),
27356    ));
27357
27358    // Test hierarchical sibling navigation
27359    let text = r#"
27360        fn outer() {
27361            if condition {
27362                let a = 1;
27363            }
27364            let b = 2;
27365        }
27366
27367        fn another() {
27368            let c = 3;
27369        }
27370    "#;
27371
27372    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language(language, cx));
27373    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27374    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27375
27376    // Wait for parsing to complete
27377    editor
27378        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27379        .await;
27380
27381    editor.update_in(cx, |editor, window, cx| {
27382        // Start by selecting "let a = 1;" inside the if block
27383        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27384            s.select_display_ranges([
27385                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27386            ]);
27387        });
27388
27389        let initial_selection = editor
27390            .selections
27391            .display_ranges(&editor.display_snapshot(cx));
27392        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27393
27394        // Test select next sibling - should move up levels to find the next sibling
27395        // Since "let a = 1;" has no siblings in the if block, it should move up
27396        // to find "let b = 2;" which is a sibling of the if block
27397        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27398        let next_selection = editor
27399            .selections
27400            .display_ranges(&editor.display_snapshot(cx));
27401
27402        // Should have a selection and it should be different from the initial
27403        assert_eq!(
27404            next_selection.len(),
27405            1,
27406            "Should have one selection after next"
27407        );
27408        assert_ne!(
27409            next_selection[0], initial_selection[0],
27410            "Next sibling selection should be different"
27411        );
27412
27413        // Test hierarchical navigation by going to the end of the current function
27414        // and trying to navigate to the next function
27415        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27416            s.select_display_ranges([
27417                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27418            ]);
27419        });
27420
27421        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27422        let function_next_selection = editor
27423            .selections
27424            .display_ranges(&editor.display_snapshot(cx));
27425
27426        // Should move to the next function
27427        assert_eq!(
27428            function_next_selection.len(),
27429            1,
27430            "Should have one selection after function next"
27431        );
27432
27433        // Test select previous sibling navigation
27434        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27435        let prev_selection = editor
27436            .selections
27437            .display_ranges(&editor.display_snapshot(cx));
27438
27439        // Should have a selection and it should be different
27440        assert_eq!(
27441            prev_selection.len(),
27442            1,
27443            "Should have one selection after prev"
27444        );
27445        assert_ne!(
27446            prev_selection[0], function_next_selection[0],
27447            "Previous sibling selection should be different from next"
27448        );
27449    });
27450}
27451
27452#[gpui::test]
27453async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27454    init_test(cx, |_| {});
27455
27456    let mut cx = EditorTestContext::new(cx).await;
27457    cx.set_state(
27458        "let ˇvariable = 42;
27459let another = variable + 1;
27460let result = variable * 2;",
27461    );
27462
27463    // Set up document highlights manually (simulating LSP response)
27464    cx.update_editor(|editor, _window, cx| {
27465        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27466
27467        // Create highlights for "variable" occurrences
27468        let highlight_ranges = [
27469            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27470            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27471            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27472        ];
27473
27474        let anchor_ranges: Vec<_> = highlight_ranges
27475            .iter()
27476            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27477            .collect();
27478
27479        editor.highlight_background::<DocumentHighlightRead>(
27480            &anchor_ranges,
27481            |_, theme| theme.colors().editor_document_highlight_read_background,
27482            cx,
27483        );
27484    });
27485
27486    // Go to next highlight - should move to second "variable"
27487    cx.update_editor(|editor, window, cx| {
27488        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27489    });
27490    cx.assert_editor_state(
27491        "let variable = 42;
27492let another = ˇvariable + 1;
27493let result = variable * 2;",
27494    );
27495
27496    // Go to next highlight - should move to third "variable"
27497    cx.update_editor(|editor, window, cx| {
27498        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27499    });
27500    cx.assert_editor_state(
27501        "let variable = 42;
27502let another = variable + 1;
27503let result = ˇvariable * 2;",
27504    );
27505
27506    // Go to next highlight - should stay at third "variable" (no wrap-around)
27507    cx.update_editor(|editor, window, cx| {
27508        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27509    });
27510    cx.assert_editor_state(
27511        "let variable = 42;
27512let another = variable + 1;
27513let result = ˇvariable * 2;",
27514    );
27515
27516    // Now test going backwards from third position
27517    cx.update_editor(|editor, window, cx| {
27518        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27519    });
27520    cx.assert_editor_state(
27521        "let variable = 42;
27522let another = ˇvariable + 1;
27523let result = variable * 2;",
27524    );
27525
27526    // Go to previous highlight - should move to first "variable"
27527    cx.update_editor(|editor, window, cx| {
27528        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27529    });
27530    cx.assert_editor_state(
27531        "let ˇvariable = 42;
27532let another = variable + 1;
27533let result = variable * 2;",
27534    );
27535
27536    // Go to previous highlight - should stay on first "variable"
27537    cx.update_editor(|editor, window, cx| {
27538        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27539    });
27540    cx.assert_editor_state(
27541        "let ˇvariable = 42;
27542let another = variable + 1;
27543let result = variable * 2;",
27544    );
27545}
27546
27547#[gpui::test]
27548async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27549    cx: &mut gpui::TestAppContext,
27550) {
27551    init_test(cx, |_| {});
27552
27553    let url = "https://zed.dev";
27554
27555    let markdown_language = Arc::new(Language::new(
27556        LanguageConfig {
27557            name: "Markdown".into(),
27558            ..LanguageConfig::default()
27559        },
27560        None,
27561    ));
27562
27563    let mut cx = EditorTestContext::new(cx).await;
27564    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27565    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27566
27567    cx.update_editor(|editor, window, cx| {
27568        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27569        editor.paste(&Paste, window, cx);
27570    });
27571
27572    cx.assert_editor_state(&format!(
27573        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27574    ));
27575}
27576
27577#[gpui::test]
27578async fn test_markdown_indents(cx: &mut gpui::TestAppContext) {
27579    init_test(cx, |_| {});
27580
27581    let markdown_language = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
27582    let mut cx = EditorTestContext::new(cx).await;
27583
27584    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27585
27586    // Case 1: Test if adding a character with multi cursors preserves nested list indents
27587    cx.set_state(&indoc! {"
27588        - [ ] Item 1
27589            - [ ] Item 1.a
27590        - [ˇ] Item 2
27591            - [ˇ] Item 2.a
27592            - [ˇ] Item 2.b
27593        "
27594    });
27595    cx.update_editor(|editor, window, cx| {
27596        editor.handle_input("x", window, cx);
27597    });
27598    cx.assert_editor_state(indoc! {"
27599        - [ ] Item 1
27600            - [ ] Item 1.a
27601        - [xˇ] Item 2
27602            - [xˇ] Item 2.a
27603            - [xˇ] Item 2.b
27604        "
27605    });
27606
27607    // Case 2: Test adding new line after nested list preserves indent of previous line
27608    cx.set_state(&indoc! {"
27609        - [ ] Item 1
27610            - [ ] Item 1.a
27611        - [x] Item 2
27612            - [x] Item 2.a
27613            - [x] Item 2.bˇ
27614        "
27615    });
27616    cx.update_editor(|editor, window, cx| {
27617        editor.newline(&Newline, window, cx);
27618    });
27619    cx.assert_editor_state(indoc! {"
27620        - [ ] Item 1
27621            - [ ] Item 1.a
27622        - [x] Item 2
27623            - [x] Item 2.a
27624            - [x] Item 2.b
27625            ˇ
27626        "
27627    });
27628
27629    // Case 3: Test adding a new nested list item preserves indent
27630    cx.update_editor(|editor, window, cx| {
27631        editor.handle_input("-", window, cx);
27632    });
27633    cx.assert_editor_state(indoc! {"
27634        - [ ] Item 1
27635            - [ ] Item 1.a
27636        - [x] Item 2
27637            - [x] Item 2.a
27638            - [x] Item 2.b
2763927640        "
27641    });
27642    cx.update_editor(|editor, window, cx| {
27643        editor.handle_input(" [x] Item 2.c", window, cx);
27644    });
27645    cx.assert_editor_state(indoc! {"
27646        - [ ] Item 1
27647            - [ ] Item 1.a
27648        - [x] Item 2
27649            - [x] Item 2.a
27650            - [x] Item 2.b
27651            - [x] Item 2.cˇ
27652        "
27653    });
27654
27655    // Case 4: Test adding new line after nested ordered list preserves indent of previous line
27656    cx.set_state(indoc! {"
27657        1. Item 1
27658            1. Item 1.a
27659        2. Item 2
27660            1. Item 2.a
27661            2. Item 2.bˇ
27662        "
27663    });
27664    cx.update_editor(|editor, window, cx| {
27665        editor.newline(&Newline, window, cx);
27666    });
27667    cx.assert_editor_state(indoc! {"
27668        1. Item 1
27669            1. Item 1.a
27670        2. Item 2
27671            1. Item 2.a
27672            2. Item 2.b
27673            ˇ
27674        "
27675    });
27676
27677    // Case 5: Adding new ordered list item preserves indent
27678    cx.update_editor(|editor, window, cx| {
27679        editor.handle_input("3", window, cx);
27680    });
27681    cx.assert_editor_state(indoc! {"
27682        1. Item 1
27683            1. Item 1.a
27684        2. Item 2
27685            1. Item 2.a
27686            2. Item 2.b
2768727688        "
27689    });
27690    cx.update_editor(|editor, window, cx| {
27691        editor.handle_input(".", window, cx);
27692    });
27693    cx.assert_editor_state(indoc! {"
27694        1. Item 1
27695            1. Item 1.a
27696        2. Item 2
27697            1. Item 2.a
27698            2. Item 2.b
27699            3.ˇ
27700        "
27701    });
27702    cx.update_editor(|editor, window, cx| {
27703        editor.handle_input(" Item 2.c", window, cx);
27704    });
27705    cx.assert_editor_state(indoc! {"
27706        1. Item 1
27707            1. Item 1.a
27708        2. Item 2
27709            1. Item 2.a
27710            2. Item 2.b
27711            3. Item 2.cˇ
27712        "
27713    });
27714
27715    // Case 7: Test blockquote newline preserves something
27716    cx.set_state(indoc! {"
27717        > Item 1ˇ
27718        "
27719    });
27720    cx.update_editor(|editor, window, cx| {
27721        editor.newline(&Newline, window, cx);
27722    });
27723    cx.assert_editor_state(indoc! {"
27724        > Item 1
27725        ˇ
27726        "
27727    });
27728}
27729
27730#[gpui::test]
27731async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27732    cx: &mut gpui::TestAppContext,
27733) {
27734    init_test(cx, |_| {});
27735
27736    let url = "https://zed.dev";
27737
27738    let markdown_language = Arc::new(Language::new(
27739        LanguageConfig {
27740            name: "Markdown".into(),
27741            ..LanguageConfig::default()
27742        },
27743        None,
27744    ));
27745
27746    let mut cx = EditorTestContext::new(cx).await;
27747    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27748    cx.set_state(&format!(
27749        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27750    ));
27751
27752    cx.update_editor(|editor, window, cx| {
27753        editor.copy(&Copy, window, cx);
27754    });
27755
27756    cx.set_state(&format!(
27757        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27758    ));
27759
27760    cx.update_editor(|editor, window, cx| {
27761        editor.paste(&Paste, window, cx);
27762    });
27763
27764    cx.assert_editor_state(&format!(
27765        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27766    ));
27767}
27768
27769#[gpui::test]
27770async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27771    cx: &mut gpui::TestAppContext,
27772) {
27773    init_test(cx, |_| {});
27774
27775    let url = "https://zed.dev";
27776
27777    let markdown_language = Arc::new(Language::new(
27778        LanguageConfig {
27779            name: "Markdown".into(),
27780            ..LanguageConfig::default()
27781        },
27782        None,
27783    ));
27784
27785    let mut cx = EditorTestContext::new(cx).await;
27786    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27787    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27788
27789    cx.update_editor(|editor, window, cx| {
27790        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27791        editor.paste(&Paste, window, cx);
27792    });
27793
27794    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27795}
27796
27797#[gpui::test]
27798async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27799    cx: &mut gpui::TestAppContext,
27800) {
27801    init_test(cx, |_| {});
27802
27803    let text = "Awesome";
27804
27805    let markdown_language = Arc::new(Language::new(
27806        LanguageConfig {
27807            name: "Markdown".into(),
27808            ..LanguageConfig::default()
27809        },
27810        None,
27811    ));
27812
27813    let mut cx = EditorTestContext::new(cx).await;
27814    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27815    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27816
27817    cx.update_editor(|editor, window, cx| {
27818        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27819        editor.paste(&Paste, window, cx);
27820    });
27821
27822    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27823}
27824
27825#[gpui::test]
27826async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27827    cx: &mut gpui::TestAppContext,
27828) {
27829    init_test(cx, |_| {});
27830
27831    let url = "https://zed.dev";
27832
27833    let markdown_language = Arc::new(Language::new(
27834        LanguageConfig {
27835            name: "Rust".into(),
27836            ..LanguageConfig::default()
27837        },
27838        None,
27839    ));
27840
27841    let mut cx = EditorTestContext::new(cx).await;
27842    cx.update_buffer(|buffer, cx| buffer.set_language(Some(markdown_language), cx));
27843    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27844
27845    cx.update_editor(|editor, window, cx| {
27846        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27847        editor.paste(&Paste, window, cx);
27848    });
27849
27850    cx.assert_editor_state(&format!(
27851        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27852    ));
27853}
27854
27855#[gpui::test]
27856async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27857    cx: &mut TestAppContext,
27858) {
27859    init_test(cx, |_| {});
27860
27861    let url = "https://zed.dev";
27862
27863    let markdown_language = Arc::new(Language::new(
27864        LanguageConfig {
27865            name: "Markdown".into(),
27866            ..LanguageConfig::default()
27867        },
27868        None,
27869    ));
27870
27871    let (editor, cx) = cx.add_window_view(|window, cx| {
27872        let multi_buffer = MultiBuffer::build_multi(
27873            [
27874                ("this will embed -> link", vec![Point::row_range(0..1)]),
27875                ("this will replace -> link", vec![Point::row_range(0..1)]),
27876            ],
27877            cx,
27878        );
27879        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
27880        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27881            s.select_ranges(vec![
27882                Point::new(0, 19)..Point::new(0, 23),
27883                Point::new(1, 21)..Point::new(1, 25),
27884            ])
27885        });
27886        let first_buffer_id = multi_buffer
27887            .read(cx)
27888            .excerpt_buffer_ids()
27889            .into_iter()
27890            .next()
27891            .unwrap();
27892        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
27893        first_buffer.update(cx, |buffer, cx| {
27894            buffer.set_language(Some(markdown_language.clone()), cx);
27895        });
27896
27897        editor
27898    });
27899    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27900
27901    cx.update_editor(|editor, window, cx| {
27902        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27903        editor.paste(&Paste, window, cx);
27904    });
27905
27906    cx.assert_editor_state(&format!(
27907        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
27908    ));
27909}
27910
27911#[gpui::test]
27912async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
27913    init_test(cx, |_| {});
27914
27915    let fs = FakeFs::new(cx.executor());
27916    fs.insert_tree(
27917        path!("/project"),
27918        json!({
27919            "first.rs": "# First Document\nSome content here.",
27920            "second.rs": "Plain text content for second file.",
27921        }),
27922    )
27923    .await;
27924
27925    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
27926    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27927    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27928
27929    let language = rust_lang();
27930    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27931    language_registry.add(language.clone());
27932    let mut fake_servers = language_registry.register_fake_lsp(
27933        "Rust",
27934        FakeLspAdapter {
27935            ..FakeLspAdapter::default()
27936        },
27937    );
27938
27939    let buffer1 = project
27940        .update(cx, |project, cx| {
27941            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
27942        })
27943        .await
27944        .unwrap();
27945    let buffer2 = project
27946        .update(cx, |project, cx| {
27947            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
27948        })
27949        .await
27950        .unwrap();
27951
27952    let multi_buffer = cx.new(|cx| {
27953        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
27954        multi_buffer.set_excerpts_for_path(
27955            PathKey::for_buffer(&buffer1, cx),
27956            buffer1.clone(),
27957            [Point::zero()..buffer1.read(cx).max_point()],
27958            3,
27959            cx,
27960        );
27961        multi_buffer.set_excerpts_for_path(
27962            PathKey::for_buffer(&buffer2, cx),
27963            buffer2.clone(),
27964            [Point::zero()..buffer1.read(cx).max_point()],
27965            3,
27966            cx,
27967        );
27968        multi_buffer
27969    });
27970
27971    let (editor, cx) = cx.add_window_view(|window, cx| {
27972        Editor::new(
27973            EditorMode::full(),
27974            multi_buffer,
27975            Some(project.clone()),
27976            window,
27977            cx,
27978        )
27979    });
27980
27981    let fake_language_server = fake_servers.next().await.unwrap();
27982
27983    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
27984
27985    let save = editor.update_in(cx, |editor, window, cx| {
27986        assert!(editor.is_dirty(cx));
27987
27988        editor.save(
27989            SaveOptions {
27990                format: true,
27991                autosave: true,
27992            },
27993            project,
27994            window,
27995            cx,
27996        )
27997    });
27998    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27999    let (done_edit_tx, done_edit_rx) = oneshot::channel();
28000    let mut done_edit_rx = Some(done_edit_rx);
28001    let mut start_edit_tx = Some(start_edit_tx);
28002
28003    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
28004        start_edit_tx.take().unwrap().send(()).unwrap();
28005        let done_edit_rx = done_edit_rx.take().unwrap();
28006        async move {
28007            done_edit_rx.await.unwrap();
28008            Ok(None)
28009        }
28010    });
28011
28012    start_edit_rx.await.unwrap();
28013    buffer2
28014        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
28015        .unwrap();
28016
28017    done_edit_tx.send(()).unwrap();
28018
28019    save.await.unwrap();
28020    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
28021}
28022
28023#[track_caller]
28024fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
28025    editor
28026        .all_inlays(cx)
28027        .into_iter()
28028        .filter_map(|inlay| inlay.get_color())
28029        .map(Rgba::from)
28030        .collect()
28031}
28032
28033#[gpui::test]
28034fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
28035    init_test(cx, |_| {});
28036
28037    let editor = cx.add_window(|window, cx| {
28038        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
28039        build_editor(buffer, window, cx)
28040    });
28041
28042    editor
28043        .update(cx, |editor, window, cx| {
28044            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
28045                s.select_display_ranges([
28046                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
28047                ])
28048            });
28049
28050            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
28051
28052            assert_eq!(
28053                editor.display_text(cx),
28054                "line1\nline2\nline2",
28055                "Duplicating last line upward should create duplicate above, not on same line"
28056            );
28057
28058            assert_eq!(
28059                editor
28060                    .selections
28061                    .display_ranges(&editor.display_snapshot(cx)),
28062                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
28063                "Selection should move to the duplicated line"
28064            );
28065        })
28066        .unwrap();
28067}
28068
28069#[gpui::test]
28070async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
28071    init_test(cx, |_| {});
28072
28073    let mut cx = EditorTestContext::new(cx).await;
28074
28075    cx.set_state("line1\nline2ˇ");
28076
28077    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28078
28079    let clipboard_text = cx
28080        .read_from_clipboard()
28081        .and_then(|item| item.text().as_deref().map(str::to_string));
28082
28083    assert_eq!(
28084        clipboard_text,
28085        Some("line2\n".to_string()),
28086        "Copying a line without trailing newline should include a newline"
28087    );
28088
28089    cx.set_state("line1\nˇ");
28090
28091    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28092
28093    cx.assert_editor_state("line1\nline2\nˇ");
28094}
28095
28096#[gpui::test]
28097async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28098    init_test(cx, |_| {});
28099
28100    let mut cx = EditorTestContext::new(cx).await;
28101
28102    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28103
28104    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
28105
28106    let clipboard_text = cx
28107        .read_from_clipboard()
28108        .and_then(|item| item.text().as_deref().map(str::to_string));
28109
28110    assert_eq!(
28111        clipboard_text,
28112        Some("line1\nline2\nline3\n".to_string()),
28113        "Copying multiple lines should include a single newline between lines"
28114    );
28115
28116    cx.set_state("lineA\nˇ");
28117
28118    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28119
28120    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28121}
28122
28123#[gpui::test]
28124async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
28125    init_test(cx, |_| {});
28126
28127    let mut cx = EditorTestContext::new(cx).await;
28128
28129    cx.set_state("ˇline1\nˇline2\nˇline3\n");
28130
28131    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
28132
28133    let clipboard_text = cx
28134        .read_from_clipboard()
28135        .and_then(|item| item.text().as_deref().map(str::to_string));
28136
28137    assert_eq!(
28138        clipboard_text,
28139        Some("line1\nline2\nline3\n".to_string()),
28140        "Copying multiple lines should include a single newline between lines"
28141    );
28142
28143    cx.set_state("lineA\nˇ");
28144
28145    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
28146
28147    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
28148}
28149
28150#[gpui::test]
28151async fn test_end_of_editor_context(cx: &mut TestAppContext) {
28152    init_test(cx, |_| {});
28153
28154    let mut cx = EditorTestContext::new(cx).await;
28155
28156    cx.set_state("line1\nline2ˇ");
28157    cx.update_editor(|e, window, cx| {
28158        e.set_mode(EditorMode::SingleLine);
28159        assert!(e.key_context(window, cx).contains("end_of_input"));
28160    });
28161    cx.set_state("ˇline1\nline2");
28162    cx.update_editor(|e, window, cx| {
28163        assert!(!e.key_context(window, cx).contains("end_of_input"));
28164    });
28165    cx.set_state("line1ˇ\nline2");
28166    cx.update_editor(|e, window, cx| {
28167        assert!(!e.key_context(window, cx).contains("end_of_input"));
28168    });
28169}
28170
28171#[gpui::test]
28172async fn test_sticky_scroll(cx: &mut TestAppContext) {
28173    init_test(cx, |_| {});
28174    let mut cx = EditorTestContext::new(cx).await;
28175
28176    let buffer = indoc! {"
28177            ˇfn foo() {
28178                let abc = 123;
28179            }
28180            struct Bar;
28181            impl Bar {
28182                fn new() -> Self {
28183                    Self
28184                }
28185            }
28186            fn baz() {
28187            }
28188        "};
28189    cx.set_state(&buffer);
28190
28191    cx.update_editor(|e, _, cx| {
28192        e.buffer()
28193            .read(cx)
28194            .as_singleton()
28195            .unwrap()
28196            .update(cx, |buffer, cx| {
28197                buffer.set_language(Some(rust_lang()), cx);
28198            })
28199    });
28200
28201    let mut sticky_headers = |offset: ScrollOffset| {
28202        cx.update_editor(|e, window, cx| {
28203            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
28204            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
28205                .into_iter()
28206                .map(
28207                    |StickyHeader {
28208                         start_point,
28209                         offset,
28210                         ..
28211                     }| { (start_point, offset) },
28212                )
28213                .collect::<Vec<_>>()
28214        })
28215    };
28216
28217    let fn_foo = Point { row: 0, column: 0 };
28218    let impl_bar = Point { row: 4, column: 0 };
28219    let fn_new = Point { row: 5, column: 4 };
28220
28221    assert_eq!(sticky_headers(0.0), vec![]);
28222    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
28223    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
28224    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
28225    assert_eq!(sticky_headers(2.0), vec![]);
28226    assert_eq!(sticky_headers(2.5), vec![]);
28227    assert_eq!(sticky_headers(3.0), vec![]);
28228    assert_eq!(sticky_headers(3.5), vec![]);
28229    assert_eq!(sticky_headers(4.0), vec![]);
28230    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28231    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
28232    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
28233    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
28234    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
28235    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
28236    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
28237    assert_eq!(sticky_headers(8.0), vec![]);
28238    assert_eq!(sticky_headers(8.5), vec![]);
28239    assert_eq!(sticky_headers(9.0), vec![]);
28240    assert_eq!(sticky_headers(9.5), vec![]);
28241    assert_eq!(sticky_headers(10.0), vec![]);
28242}
28243
28244#[gpui::test]
28245async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
28246    init_test(cx, |_| {});
28247    cx.update(|cx| {
28248        SettingsStore::update_global(cx, |store, cx| {
28249            store.update_user_settings(cx, |settings| {
28250                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
28251                    enabled: Some(true),
28252                })
28253            });
28254        });
28255    });
28256    let mut cx = EditorTestContext::new(cx).await;
28257
28258    let line_height = cx.editor(|editor, window, _cx| {
28259        editor
28260            .style()
28261            .unwrap()
28262            .text
28263            .line_height_in_pixels(window.rem_size())
28264    });
28265
28266    let buffer = indoc! {"
28267            ˇfn foo() {
28268                let abc = 123;
28269            }
28270            struct Bar;
28271            impl Bar {
28272                fn new() -> Self {
28273                    Self
28274                }
28275            }
28276            fn baz() {
28277            }
28278        "};
28279    cx.set_state(&buffer);
28280
28281    cx.update_editor(|e, _, cx| {
28282        e.buffer()
28283            .read(cx)
28284            .as_singleton()
28285            .unwrap()
28286            .update(cx, |buffer, cx| {
28287                buffer.set_language(Some(rust_lang()), cx);
28288            })
28289    });
28290
28291    let fn_foo = || empty_range(0, 0);
28292    let impl_bar = || empty_range(4, 0);
28293    let fn_new = || empty_range(5, 4);
28294
28295    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
28296        cx.update_editor(|e, window, cx| {
28297            e.scroll(
28298                gpui::Point {
28299                    x: 0.,
28300                    y: scroll_offset,
28301                },
28302                None,
28303                window,
28304                cx,
28305            );
28306        });
28307        cx.simulate_click(
28308            gpui::Point {
28309                x: px(0.),
28310                y: click_offset as f32 * line_height,
28311            },
28312            Modifiers::none(),
28313        );
28314        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
28315    };
28316
28317    assert_eq!(
28318        scroll_and_click(
28319            4.5, // impl Bar is halfway off the screen
28320            0.0  // click top of screen
28321        ),
28322        // scrolled to impl Bar
28323        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28324    );
28325
28326    assert_eq!(
28327        scroll_and_click(
28328            4.5,  // impl Bar is halfway off the screen
28329            0.25  // click middle of impl Bar
28330        ),
28331        // scrolled to impl Bar
28332        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28333    );
28334
28335    assert_eq!(
28336        scroll_and_click(
28337            4.5, // impl Bar is halfway off the screen
28338            1.5  // click below impl Bar (e.g. fn new())
28339        ),
28340        // scrolled to fn new() - this is below the impl Bar header which has persisted
28341        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28342    );
28343
28344    assert_eq!(
28345        scroll_and_click(
28346            5.5,  // fn new is halfway underneath impl Bar
28347            0.75  // click on the overlap of impl Bar and fn new()
28348        ),
28349        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
28350    );
28351
28352    assert_eq!(
28353        scroll_and_click(
28354            5.5,  // fn new is halfway underneath impl Bar
28355            1.25  // click on the visible part of fn new()
28356        ),
28357        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
28358    );
28359
28360    assert_eq!(
28361        scroll_and_click(
28362            1.5, // fn foo is halfway off the screen
28363            0.0  // click top of screen
28364        ),
28365        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
28366    );
28367
28368    assert_eq!(
28369        scroll_and_click(
28370            1.5,  // fn foo is halfway off the screen
28371            0.75  // click visible part of let abc...
28372        )
28373        .0,
28374        // no change in scroll
28375        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
28376        (gpui::Point { x: 0., y: 1.5 })
28377    );
28378}
28379
28380#[gpui::test]
28381async fn test_next_prev_reference(cx: &mut TestAppContext) {
28382    const CYCLE_POSITIONS: &[&'static str] = &[
28383        indoc! {"
28384            fn foo() {
28385                let ˇabc = 123;
28386                let x = abc + 1;
28387                let y = abc + 2;
28388                let z = abc + 2;
28389            }
28390        "},
28391        indoc! {"
28392            fn foo() {
28393                let abc = 123;
28394                let x = ˇabc + 1;
28395                let y = abc + 2;
28396                let z = abc + 2;
28397            }
28398        "},
28399        indoc! {"
28400            fn foo() {
28401                let abc = 123;
28402                let x = abc + 1;
28403                let y = ˇabc + 2;
28404                let z = abc + 2;
28405            }
28406        "},
28407        indoc! {"
28408            fn foo() {
28409                let abc = 123;
28410                let x = abc + 1;
28411                let y = abc + 2;
28412                let z = ˇabc + 2;
28413            }
28414        "},
28415    ];
28416
28417    init_test(cx, |_| {});
28418
28419    let mut cx = EditorLspTestContext::new_rust(
28420        lsp::ServerCapabilities {
28421            references_provider: Some(lsp::OneOf::Left(true)),
28422            ..Default::default()
28423        },
28424        cx,
28425    )
28426    .await;
28427
28428    // importantly, the cursor is in the middle
28429    cx.set_state(indoc! {"
28430        fn foo() {
28431            let aˇbc = 123;
28432            let x = abc + 1;
28433            let y = abc + 2;
28434            let z = abc + 2;
28435        }
28436    "});
28437
28438    let reference_ranges = [
28439        lsp::Position::new(1, 8),
28440        lsp::Position::new(2, 12),
28441        lsp::Position::new(3, 12),
28442        lsp::Position::new(4, 12),
28443    ]
28444    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
28445
28446    cx.lsp
28447        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
28448            Ok(Some(
28449                reference_ranges
28450                    .map(|range| lsp::Location {
28451                        uri: params.text_document_position.text_document.uri.clone(),
28452                        range,
28453                    })
28454                    .to_vec(),
28455            ))
28456        });
28457
28458    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
28459        cx.update_editor(|editor, window, cx| {
28460            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
28461        })
28462        .unwrap()
28463        .await
28464        .unwrap()
28465    };
28466
28467    _move(Direction::Next, 1, &mut cx).await;
28468    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28469
28470    _move(Direction::Next, 1, &mut cx).await;
28471    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28472
28473    _move(Direction::Next, 1, &mut cx).await;
28474    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28475
28476    // loops back to the start
28477    _move(Direction::Next, 1, &mut cx).await;
28478    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28479
28480    // loops back to the end
28481    _move(Direction::Prev, 1, &mut cx).await;
28482    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28483
28484    _move(Direction::Prev, 1, &mut cx).await;
28485    cx.assert_editor_state(CYCLE_POSITIONS[2]);
28486
28487    _move(Direction::Prev, 1, &mut cx).await;
28488    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28489
28490    _move(Direction::Prev, 1, &mut cx).await;
28491    cx.assert_editor_state(CYCLE_POSITIONS[0]);
28492
28493    _move(Direction::Next, 3, &mut cx).await;
28494    cx.assert_editor_state(CYCLE_POSITIONS[3]);
28495
28496    _move(Direction::Prev, 2, &mut cx).await;
28497    cx.assert_editor_state(CYCLE_POSITIONS[1]);
28498}
28499
28500#[gpui::test]
28501async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
28502    init_test(cx, |_| {});
28503
28504    let (editor, cx) = cx.add_window_view(|window, cx| {
28505        let multi_buffer = MultiBuffer::build_multi(
28506            [
28507                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28508                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28509            ],
28510            cx,
28511        );
28512        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28513    });
28514
28515    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28516    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28517
28518    cx.assert_excerpts_with_selections(indoc! {"
28519        [EXCERPT]
28520        ˇ1
28521        2
28522        3
28523        [EXCERPT]
28524        1
28525        2
28526        3
28527        "});
28528
28529    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28530    cx.update_editor(|editor, window, cx| {
28531        editor.change_selections(None.into(), window, cx, |s| {
28532            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28533        });
28534    });
28535    cx.assert_excerpts_with_selections(indoc! {"
28536        [EXCERPT]
28537        1
2853828539        3
28540        [EXCERPT]
28541        1
28542        2
28543        3
28544        "});
28545
28546    cx.update_editor(|editor, window, cx| {
28547        editor
28548            .select_all_matches(&SelectAllMatches, window, cx)
28549            .unwrap();
28550    });
28551    cx.assert_excerpts_with_selections(indoc! {"
28552        [EXCERPT]
28553        1
2855428555        3
28556        [EXCERPT]
28557        1
2855828559        3
28560        "});
28561
28562    cx.update_editor(|editor, window, cx| {
28563        editor.handle_input("X", window, cx);
28564    });
28565    cx.assert_excerpts_with_selections(indoc! {"
28566        [EXCERPT]
28567        1
2856828569        3
28570        [EXCERPT]
28571        1
2857228573        3
28574        "});
28575
28576    // Scenario 2: Select "2", then fold second buffer before insertion
28577    cx.update_multibuffer(|mb, cx| {
28578        for buffer_id in buffer_ids.iter() {
28579            let buffer = mb.buffer(*buffer_id).unwrap();
28580            buffer.update(cx, |buffer, cx| {
28581                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28582            });
28583        }
28584    });
28585
28586    // Select "2" and select all matches
28587    cx.update_editor(|editor, window, cx| {
28588        editor.change_selections(None.into(), window, cx, |s| {
28589            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28590        });
28591        editor
28592            .select_all_matches(&SelectAllMatches, window, cx)
28593            .unwrap();
28594    });
28595
28596    // Fold second buffer - should remove selections from folded buffer
28597    cx.update_editor(|editor, _, cx| {
28598        editor.fold_buffer(buffer_ids[1], cx);
28599    });
28600    cx.assert_excerpts_with_selections(indoc! {"
28601        [EXCERPT]
28602        1
2860328604        3
28605        [EXCERPT]
28606        [FOLDED]
28607        "});
28608
28609    // Insert text - should only affect first buffer
28610    cx.update_editor(|editor, window, cx| {
28611        editor.handle_input("Y", window, cx);
28612    });
28613    cx.update_editor(|editor, _, cx| {
28614        editor.unfold_buffer(buffer_ids[1], cx);
28615    });
28616    cx.assert_excerpts_with_selections(indoc! {"
28617        [EXCERPT]
28618        1
2861928620        3
28621        [EXCERPT]
28622        1
28623        2
28624        3
28625        "});
28626
28627    // Scenario 3: Select "2", then fold first buffer before insertion
28628    cx.update_multibuffer(|mb, cx| {
28629        for buffer_id in buffer_ids.iter() {
28630            let buffer = mb.buffer(*buffer_id).unwrap();
28631            buffer.update(cx, |buffer, cx| {
28632                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28633            });
28634        }
28635    });
28636
28637    // Select "2" and select all matches
28638    cx.update_editor(|editor, window, cx| {
28639        editor.change_selections(None.into(), window, cx, |s| {
28640            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28641        });
28642        editor
28643            .select_all_matches(&SelectAllMatches, window, cx)
28644            .unwrap();
28645    });
28646
28647    // Fold first buffer - should remove selections from folded buffer
28648    cx.update_editor(|editor, _, cx| {
28649        editor.fold_buffer(buffer_ids[0], cx);
28650    });
28651    cx.assert_excerpts_with_selections(indoc! {"
28652        [EXCERPT]
28653        [FOLDED]
28654        [EXCERPT]
28655        1
2865628657        3
28658        "});
28659
28660    // Insert text - should only affect second buffer
28661    cx.update_editor(|editor, window, cx| {
28662        editor.handle_input("Z", window, cx);
28663    });
28664    cx.update_editor(|editor, _, cx| {
28665        editor.unfold_buffer(buffer_ids[0], cx);
28666    });
28667    cx.assert_excerpts_with_selections(indoc! {"
28668        [EXCERPT]
28669        1
28670        2
28671        3
28672        [EXCERPT]
28673        1
2867428675        3
28676        "});
28677
28678    // Test correct folded header is selected upon fold
28679    cx.update_editor(|editor, _, cx| {
28680        editor.fold_buffer(buffer_ids[0], cx);
28681        editor.fold_buffer(buffer_ids[1], cx);
28682    });
28683    cx.assert_excerpts_with_selections(indoc! {"
28684        [EXCERPT]
28685        [FOLDED]
28686        [EXCERPT]
28687        ˇ[FOLDED]
28688        "});
28689
28690    // Test selection inside folded buffer unfolds it on type
28691    cx.update_editor(|editor, window, cx| {
28692        editor.handle_input("W", window, cx);
28693    });
28694    cx.update_editor(|editor, _, cx| {
28695        editor.unfold_buffer(buffer_ids[0], cx);
28696    });
28697    cx.assert_excerpts_with_selections(indoc! {"
28698        [EXCERPT]
28699        1
28700        2
28701        3
28702        [EXCERPT]
28703        Wˇ1
28704        Z
28705        3
28706        "});
28707}
28708
28709#[gpui::test]
28710async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
28711    init_test(cx, |_| {});
28712    let mut leader_cx = EditorTestContext::new(cx).await;
28713
28714    let diff_base = indoc!(
28715        r#"
28716        one
28717        two
28718        three
28719        four
28720        five
28721        six
28722        "#
28723    );
28724
28725    let initial_state = indoc!(
28726        r#"
28727        ˇone
28728        two
28729        THREE
28730        four
28731        five
28732        six
28733        "#
28734    );
28735
28736    leader_cx.set_state(initial_state);
28737
28738    leader_cx.set_head_text(&diff_base);
28739    leader_cx.run_until_parked();
28740
28741    let follower = leader_cx.update_multibuffer(|leader, cx| {
28742        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28743        leader.set_all_diff_hunks_expanded(cx);
28744        leader.get_or_create_follower(cx)
28745    });
28746    follower.update(cx, |follower, cx| {
28747        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28748        follower.set_all_diff_hunks_expanded(cx);
28749    });
28750
28751    let follower_editor =
28752        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
28753    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
28754
28755    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
28756    cx.run_until_parked();
28757
28758    leader_cx.assert_editor_state(initial_state);
28759    follower_cx.assert_editor_state(indoc! {
28760        r#"
28761        ˇone
28762        two
28763        three
28764        four
28765        five
28766        six
28767        "#
28768    });
28769
28770    follower_cx.editor(|editor, _window, cx| {
28771        assert!(editor.read_only(cx));
28772    });
28773
28774    leader_cx.update_editor(|editor, _window, cx| {
28775        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
28776    });
28777    cx.run_until_parked();
28778
28779    leader_cx.assert_editor_state(indoc! {
28780        r#"
28781        ˇone
28782        two
28783        THREE
28784        four
28785        FIVE
28786        six
28787        "#
28788    });
28789
28790    follower_cx.assert_editor_state(indoc! {
28791        r#"
28792        ˇone
28793        two
28794        three
28795        four
28796        five
28797        six
28798        "#
28799    });
28800
28801    leader_cx.update_editor(|editor, _window, cx| {
28802        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
28803    });
28804    cx.run_until_parked();
28805
28806    leader_cx.assert_editor_state(indoc! {
28807        r#"
28808        ˇone
28809        two
28810        THREE
28811        four
28812        FIVE
28813        six
28814        SEVEN"#
28815    });
28816
28817    follower_cx.assert_editor_state(indoc! {
28818        r#"
28819        ˇone
28820        two
28821        three
28822        four
28823        five
28824        six
28825        "#
28826    });
28827
28828    leader_cx.update_editor(|editor, window, cx| {
28829        editor.move_down(&MoveDown, window, cx);
28830        editor.refresh_selected_text_highlights(true, window, cx);
28831    });
28832    leader_cx.run_until_parked();
28833}
28834
28835#[gpui::test]
28836async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
28837    init_test(cx, |_| {});
28838    let base_text = "base\n";
28839    let buffer_text = "buffer\n";
28840
28841    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
28842    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
28843
28844    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
28845    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
28846    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
28847    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
28848
28849    let leader = cx.new(|cx| {
28850        let mut leader = MultiBuffer::new(Capability::ReadWrite);
28851        leader.set_all_diff_hunks_expanded(cx);
28852        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28853        leader
28854    });
28855    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
28856    follower.update(cx, |follower, _| {
28857        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28858    });
28859
28860    leader.update(cx, |leader, cx| {
28861        leader.insert_excerpts_after(
28862            ExcerptId::min(),
28863            extra_buffer_2.clone(),
28864            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28865            cx,
28866        );
28867        leader.add_diff(extra_diff_2.clone(), cx);
28868
28869        leader.insert_excerpts_after(
28870            ExcerptId::min(),
28871            extra_buffer_1.clone(),
28872            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28873            cx,
28874        );
28875        leader.add_diff(extra_diff_1.clone(), cx);
28876
28877        leader.insert_excerpts_after(
28878            ExcerptId::min(),
28879            buffer1.clone(),
28880            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28881            cx,
28882        );
28883        leader.add_diff(diff1.clone(), cx);
28884    });
28885
28886    cx.run_until_parked();
28887    let mut cx = cx.add_empty_window();
28888
28889    let leader_editor = cx
28890        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
28891    let follower_editor = cx.new_window_entity(|window, cx| {
28892        Editor::for_multibuffer(follower.clone(), None, window, cx)
28893    });
28894
28895    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
28896    leader_cx.assert_editor_state(indoc! {"
28897       ˇbuffer
28898
28899       dummy text 1
28900
28901       dummy text 2
28902    "});
28903    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
28904    follower_cx.assert_editor_state(indoc! {"
28905        ˇbase
28906
28907
28908    "});
28909}
28910
28911#[gpui::test]
28912async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
28913    init_test(cx, |_| {});
28914
28915    let (editor, cx) = cx.add_window_view(|window, cx| {
28916        let multi_buffer = MultiBuffer::build_multi(
28917            [
28918                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28919                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
28920            ],
28921            cx,
28922        );
28923        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28924    });
28925
28926    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28927
28928    cx.assert_excerpts_with_selections(indoc! {"
28929        [EXCERPT]
28930        ˇ1
28931        2
28932        3
28933        [EXCERPT]
28934        1
28935        2
28936        3
28937        4
28938        5
28939        6
28940        7
28941        8
28942        9
28943        "});
28944
28945    cx.update_editor(|editor, window, cx| {
28946        editor.change_selections(None.into(), window, cx, |s| {
28947            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
28948        });
28949    });
28950
28951    cx.assert_excerpts_with_selections(indoc! {"
28952        [EXCERPT]
28953        1
28954        2
28955        3
28956        [EXCERPT]
28957        1
28958        2
28959        3
28960        4
28961        5
28962        6
28963        ˇ7
28964        8
28965        9
28966        "});
28967
28968    cx.update_editor(|editor, _window, cx| {
28969        editor.set_vertical_scroll_margin(0, cx);
28970    });
28971
28972    cx.update_editor(|editor, window, cx| {
28973        assert_eq!(editor.vertical_scroll_margin(), 0);
28974        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28975        assert_eq!(
28976            editor.snapshot(window, cx).scroll_position(),
28977            gpui::Point::new(0., 12.0)
28978        );
28979    });
28980
28981    cx.update_editor(|editor, _window, cx| {
28982        editor.set_vertical_scroll_margin(3, cx);
28983    });
28984
28985    cx.update_editor(|editor, window, cx| {
28986        assert_eq!(editor.vertical_scroll_margin(), 3);
28987        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28988        assert_eq!(
28989            editor.snapshot(window, cx).scroll_position(),
28990            gpui::Point::new(0., 9.0)
28991        );
28992    });
28993}
28994
28995#[gpui::test]
28996async fn test_find_references_single_case(cx: &mut TestAppContext) {
28997    init_test(cx, |_| {});
28998    let mut cx = EditorLspTestContext::new_rust(
28999        lsp::ServerCapabilities {
29000            references_provider: Some(lsp::OneOf::Left(true)),
29001            ..lsp::ServerCapabilities::default()
29002        },
29003        cx,
29004    )
29005    .await;
29006
29007    let before = indoc!(
29008        r#"
29009        fn main() {
29010            let aˇbc = 123;
29011            let xyz = abc;
29012        }
29013        "#
29014    );
29015    let after = indoc!(
29016        r#"
29017        fn main() {
29018            let abc = 123;
29019            let xyz = ˇabc;
29020        }
29021        "#
29022    );
29023
29024    cx.lsp
29025        .set_request_handler::<lsp::request::References, _, _>(async move |params, _| {
29026            Ok(Some(vec![
29027                lsp::Location {
29028                    uri: params.text_document_position.text_document.uri.clone(),
29029                    range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 11)),
29030                },
29031                lsp::Location {
29032                    uri: params.text_document_position.text_document.uri,
29033                    range: lsp::Range::new(lsp::Position::new(2, 14), lsp::Position::new(2, 17)),
29034                },
29035            ]))
29036        });
29037
29038    cx.set_state(before);
29039
29040    let action = FindAllReferences {
29041        always_open_multibuffer: false,
29042    };
29043
29044    let navigated = cx
29045        .update_editor(|editor, window, cx| editor.find_all_references(&action, window, cx))
29046        .expect("should have spawned a task")
29047        .await
29048        .unwrap();
29049
29050    assert_eq!(navigated, Navigated::No);
29051
29052    cx.run_until_parked();
29053
29054    cx.assert_editor_state(after);
29055}