editor_tests.rs

    1use super::*;
    2use crate::{
    3    JoinLines,
    4    code_context_menus::CodeContextMenu,
    5    edit_prediction_tests::FakeEditPredictionProvider,
    6    element::StickyHeader,
    7    linked_editing_ranges::LinkedEditingRanges,
    8    scroll::scroll_amount::ScrollAmount,
    9    test::{
   10        assert_text_with_selections, build_editor,
   11        editor_lsp_test_context::{EditorLspTestContext, git_commit_lang},
   12        editor_test_context::EditorTestContext,
   13        select_ranges,
   14    },
   15};
   16use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkStatusKind};
   17use collections::HashMap;
   18use futures::{StreamExt, channel::oneshot};
   19use gpui::{
   20    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
   21    WindowBounds, WindowOptions, div,
   22};
   23use indoc::indoc;
   24use language::{
   25    BracketPairConfig,
   26    Capability::ReadWrite,
   27    DiagnosticSourceKind, FakeLspAdapter, IndentGuideSettings, LanguageConfig,
   28    LanguageConfigOverride, LanguageMatcher, LanguageName, Override, Point,
   29    language_settings::{
   30        CompletionSettingsContent, FormatterList, LanguageSettingsContent, LspInsertMode,
   31    },
   32    tree_sitter_python,
   33};
   34use language_settings::Formatter;
   35use languages::markdown_lang;
   36use languages::rust_lang;
   37use lsp::CompletionParams;
   38use multi_buffer::{
   39    IndentGuide, MultiBufferFilterMode, MultiBufferOffset, MultiBufferOffsetUtf16, PathKey,
   40};
   41use parking_lot::Mutex;
   42use pretty_assertions::{assert_eq, assert_ne};
   43use project::{
   44    FakeFs,
   45    debugger::breakpoint_store::{BreakpointState, SourceBreakpoint},
   46    project_settings::LspSettings,
   47};
   48use serde_json::{self, json};
   49use settings::{
   50    AllLanguageSettingsContent, EditorSettingsContent, IndentGuideBackgroundColoring,
   51    IndentGuideColoring, ProjectSettingsContent, SearchSettingsContent,
   52};
   53use std::{cell::RefCell, future::Future, rc::Rc, sync::atomic::AtomicBool, time::Instant};
   54use std::{
   55    iter,
   56    sync::atomic::{self, AtomicUsize},
   57};
   58use test::build_editor_with_project;
   59use text::ToPoint as _;
   60use unindent::Unindent;
   61use util::{
   62    assert_set_eq, path,
   63    rel_path::rel_path,
   64    test::{TextRangeMarker, marked_text_ranges, marked_text_ranges_by, sample_text},
   65    uri,
   66};
   67use workspace::{
   68    CloseActiveItem, CloseAllItems, CloseOtherItems, MoveItemToPaneInDirection, NavigationEntry,
   69    OpenOptions, ViewId,
   70    invalid_item_view::InvalidItemView,
   71    item::{FollowEvent, FollowableItem, Item, ItemHandle, SaveOptions},
   72    register_project_item,
   73};
   74
   75fn display_ranges(editor: &Editor, cx: &mut Context<'_, Editor>) -> Vec<Range<DisplayPoint>> {
   76    editor
   77        .selections
   78        .display_ranges(&editor.display_snapshot(cx))
   79}
   80
   81#[gpui::test]
   82fn test_edit_events(cx: &mut TestAppContext) {
   83    init_test(cx, |_| {});
   84
   85    let buffer = cx.new(|cx| {
   86        let mut buffer = language::Buffer::local("123456", cx);
   87        buffer.set_group_interval(Duration::from_secs(1));
   88        buffer
   89    });
   90
   91    let events = Rc::new(RefCell::new(Vec::new()));
   92    let editor1 = cx.add_window({
   93        let events = events.clone();
   94        |window, cx| {
   95            let entity = cx.entity();
   96            cx.subscribe_in(
   97                &entity,
   98                window,
   99                move |_, _, event: &EditorEvent, _, _| match event {
  100                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor1", "edited")),
  101                    EditorEvent::BufferEdited => {
  102                        events.borrow_mut().push(("editor1", "buffer edited"))
  103                    }
  104                    _ => {}
  105                },
  106            )
  107            .detach();
  108            Editor::for_buffer(buffer.clone(), None, window, cx)
  109        }
  110    });
  111
  112    let editor2 = cx.add_window({
  113        let events = events.clone();
  114        |window, cx| {
  115            cx.subscribe_in(
  116                &cx.entity(),
  117                window,
  118                move |_, _, event: &EditorEvent, _, _| match event {
  119                    EditorEvent::Edited { .. } => events.borrow_mut().push(("editor2", "edited")),
  120                    EditorEvent::BufferEdited => {
  121                        events.borrow_mut().push(("editor2", "buffer edited"))
  122                    }
  123                    _ => {}
  124                },
  125            )
  126            .detach();
  127            Editor::for_buffer(buffer.clone(), None, window, cx)
  128        }
  129    });
  130
  131    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  132
  133    // Mutating editor 1 will emit an `Edited` event only for that editor.
  134    _ = editor1.update(cx, |editor, window, cx| editor.insert("X", window, cx));
  135    assert_eq!(
  136        mem::take(&mut *events.borrow_mut()),
  137        [
  138            ("editor1", "edited"),
  139            ("editor1", "buffer edited"),
  140            ("editor2", "buffer edited"),
  141        ]
  142    );
  143
  144    // Mutating editor 2 will emit an `Edited` event only for that editor.
  145    _ = editor2.update(cx, |editor, window, cx| editor.delete(&Delete, window, cx));
  146    assert_eq!(
  147        mem::take(&mut *events.borrow_mut()),
  148        [
  149            ("editor2", "edited"),
  150            ("editor1", "buffer edited"),
  151            ("editor2", "buffer edited"),
  152        ]
  153    );
  154
  155    // Undoing on editor 1 will emit an `Edited` event only for that editor.
  156    _ = editor1.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  157    assert_eq!(
  158        mem::take(&mut *events.borrow_mut()),
  159        [
  160            ("editor1", "edited"),
  161            ("editor1", "buffer edited"),
  162            ("editor2", "buffer edited"),
  163        ]
  164    );
  165
  166    // Redoing on editor 1 will emit an `Edited` event only for that editor.
  167    _ = editor1.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  168    assert_eq!(
  169        mem::take(&mut *events.borrow_mut()),
  170        [
  171            ("editor1", "edited"),
  172            ("editor1", "buffer edited"),
  173            ("editor2", "buffer edited"),
  174        ]
  175    );
  176
  177    // Undoing on editor 2 will emit an `Edited` event only for that editor.
  178    _ = editor2.update(cx, |editor, window, cx| editor.undo(&Undo, window, cx));
  179    assert_eq!(
  180        mem::take(&mut *events.borrow_mut()),
  181        [
  182            ("editor2", "edited"),
  183            ("editor1", "buffer edited"),
  184            ("editor2", "buffer edited"),
  185        ]
  186    );
  187
  188    // Redoing on editor 2 will emit an `Edited` event only for that editor.
  189    _ = editor2.update(cx, |editor, window, cx| editor.redo(&Redo, window, cx));
  190    assert_eq!(
  191        mem::take(&mut *events.borrow_mut()),
  192        [
  193            ("editor2", "edited"),
  194            ("editor1", "buffer edited"),
  195            ("editor2", "buffer edited"),
  196        ]
  197    );
  198
  199    // No event is emitted when the mutation is a no-op.
  200    _ = editor2.update(cx, |editor, window, cx| {
  201        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  202            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
  203        });
  204
  205        editor.backspace(&Backspace, window, cx);
  206    });
  207    assert_eq!(mem::take(&mut *events.borrow_mut()), []);
  208}
  209
  210#[gpui::test]
  211fn test_undo_redo_with_selection_restoration(cx: &mut TestAppContext) {
  212    init_test(cx, |_| {});
  213
  214    let mut now = Instant::now();
  215    let group_interval = Duration::from_millis(1);
  216    let buffer = cx.new(|cx| {
  217        let mut buf = language::Buffer::local("123456", cx);
  218        buf.set_group_interval(group_interval);
  219        buf
  220    });
  221    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  222    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
  223
  224    _ = editor.update(cx, |editor, window, cx| {
  225        editor.start_transaction_at(now, window, cx);
  226        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  227            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(4)])
  228        });
  229
  230        editor.insert("cd", window, cx);
  231        editor.end_transaction_at(now, cx);
  232        assert_eq!(editor.text(cx), "12cd56");
  233        assert_eq!(
  234            editor.selections.ranges(&editor.display_snapshot(cx)),
  235            vec![MultiBufferOffset(4)..MultiBufferOffset(4)]
  236        );
  237
  238        editor.start_transaction_at(now, window, cx);
  239        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  240            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(5)])
  241        });
  242        editor.insert("e", window, cx);
  243        editor.end_transaction_at(now, cx);
  244        assert_eq!(editor.text(cx), "12cde6");
  245        assert_eq!(
  246            editor.selections.ranges(&editor.display_snapshot(cx)),
  247            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  248        );
  249
  250        now += group_interval + Duration::from_millis(1);
  251        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  252            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(2)])
  253        });
  254
  255        // Simulate an edit in another editor
  256        buffer.update(cx, |buffer, cx| {
  257            buffer.start_transaction_at(now, cx);
  258            buffer.edit(
  259                [(MultiBufferOffset(0)..MultiBufferOffset(1), "a")],
  260                None,
  261                cx,
  262            );
  263            buffer.edit(
  264                [(MultiBufferOffset(1)..MultiBufferOffset(1), "b")],
  265                None,
  266                cx,
  267            );
  268            buffer.end_transaction_at(now, cx);
  269        });
  270
  271        assert_eq!(editor.text(cx), "ab2cde6");
  272        assert_eq!(
  273            editor.selections.ranges(&editor.display_snapshot(cx)),
  274            vec![MultiBufferOffset(3)..MultiBufferOffset(3)]
  275        );
  276
  277        // Last transaction happened past the group interval in a different editor.
  278        // Undo it individually and don't restore selections.
  279        editor.undo(&Undo, window, cx);
  280        assert_eq!(editor.text(cx), "12cde6");
  281        assert_eq!(
  282            editor.selections.ranges(&editor.display_snapshot(cx)),
  283            vec![MultiBufferOffset(2)..MultiBufferOffset(2)]
  284        );
  285
  286        // First two transactions happened within the group interval in this editor.
  287        // Undo them together and restore selections.
  288        editor.undo(&Undo, window, cx);
  289        editor.undo(&Undo, window, cx); // Undo stack is empty here, so this is a no-op.
  290        assert_eq!(editor.text(cx), "123456");
  291        assert_eq!(
  292            editor.selections.ranges(&editor.display_snapshot(cx)),
  293            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
  294        );
  295
  296        // Redo the first two transactions together.
  297        editor.redo(&Redo, window, cx);
  298        assert_eq!(editor.text(cx), "12cde6");
  299        assert_eq!(
  300            editor.selections.ranges(&editor.display_snapshot(cx)),
  301            vec![MultiBufferOffset(5)..MultiBufferOffset(5)]
  302        );
  303
  304        // Redo the last transaction on its own.
  305        editor.redo(&Redo, window, cx);
  306        assert_eq!(editor.text(cx), "ab2cde6");
  307        assert_eq!(
  308            editor.selections.ranges(&editor.display_snapshot(cx)),
  309            vec![MultiBufferOffset(6)..MultiBufferOffset(6)]
  310        );
  311
  312        // Test empty transactions.
  313        editor.start_transaction_at(now, window, cx);
  314        editor.end_transaction_at(now, cx);
  315        editor.undo(&Undo, window, cx);
  316        assert_eq!(editor.text(cx), "12cde6");
  317    });
  318}
  319
  320#[gpui::test]
  321fn test_ime_composition(cx: &mut TestAppContext) {
  322    init_test(cx, |_| {});
  323
  324    let buffer = cx.new(|cx| {
  325        let mut buffer = language::Buffer::local("abcde", cx);
  326        // Ensure automatic grouping doesn't occur.
  327        buffer.set_group_interval(Duration::ZERO);
  328        buffer
  329    });
  330
  331    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
  332    cx.add_window(|window, cx| {
  333        let mut editor = build_editor(buffer.clone(), window, cx);
  334
  335        // Start a new IME composition.
  336        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  337        editor.replace_and_mark_text_in_range(Some(0..1), "á", None, window, cx);
  338        editor.replace_and_mark_text_in_range(Some(0..1), "ä", None, window, cx);
  339        assert_eq!(editor.text(cx), "äbcde");
  340        assert_eq!(
  341            editor.marked_text_ranges(cx),
  342            Some(vec![
  343                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  344            ])
  345        );
  346
  347        // Finalize IME composition.
  348        editor.replace_text_in_range(None, "ā", window, cx);
  349        assert_eq!(editor.text(cx), "ābcde");
  350        assert_eq!(editor.marked_text_ranges(cx), None);
  351
  352        // IME composition edits are grouped and are undone/redone at once.
  353        editor.undo(&Default::default(), window, cx);
  354        assert_eq!(editor.text(cx), "abcde");
  355        assert_eq!(editor.marked_text_ranges(cx), None);
  356        editor.redo(&Default::default(), window, cx);
  357        assert_eq!(editor.text(cx), "ābcde");
  358        assert_eq!(editor.marked_text_ranges(cx), None);
  359
  360        // Start a new IME composition.
  361        editor.replace_and_mark_text_in_range(Some(0..1), "à", None, window, cx);
  362        assert_eq!(
  363            editor.marked_text_ranges(cx),
  364            Some(vec![
  365                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(1))
  366            ])
  367        );
  368
  369        // Undoing during an IME composition cancels it.
  370        editor.undo(&Default::default(), window, cx);
  371        assert_eq!(editor.text(cx), "ābcde");
  372        assert_eq!(editor.marked_text_ranges(cx), None);
  373
  374        // Start a new IME composition with an invalid marked range, ensuring it gets clipped.
  375        editor.replace_and_mark_text_in_range(Some(4..999), "è", None, window, cx);
  376        assert_eq!(editor.text(cx), "ābcdè");
  377        assert_eq!(
  378            editor.marked_text_ranges(cx),
  379            Some(vec![
  380                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(5))
  381            ])
  382        );
  383
  384        // Finalize IME composition with an invalid replacement range, ensuring it gets clipped.
  385        editor.replace_text_in_range(Some(4..999), "ę", window, cx);
  386        assert_eq!(editor.text(cx), "ābcdę");
  387        assert_eq!(editor.marked_text_ranges(cx), None);
  388
  389        // Start a new IME composition with multiple cursors.
  390        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  391            s.select_ranges([
  392                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(1)),
  393                MultiBufferOffsetUtf16(OffsetUtf16(3))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  394                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(5)),
  395            ])
  396        });
  397        editor.replace_and_mark_text_in_range(Some(4..5), "XYZ", None, window, cx);
  398        assert_eq!(editor.text(cx), "XYZbXYZdXYZ");
  399        assert_eq!(
  400            editor.marked_text_ranges(cx),
  401            Some(vec![
  402                MultiBufferOffsetUtf16(OffsetUtf16(0))..MultiBufferOffsetUtf16(OffsetUtf16(3)),
  403                MultiBufferOffsetUtf16(OffsetUtf16(4))..MultiBufferOffsetUtf16(OffsetUtf16(7)),
  404                MultiBufferOffsetUtf16(OffsetUtf16(8))..MultiBufferOffsetUtf16(OffsetUtf16(11))
  405            ])
  406        );
  407
  408        // Ensure the newly-marked range gets treated as relative to the previously-marked ranges.
  409        editor.replace_and_mark_text_in_range(Some(1..2), "1", None, window, cx);
  410        assert_eq!(editor.text(cx), "X1ZbX1ZdX1Z");
  411        assert_eq!(
  412            editor.marked_text_ranges(cx),
  413            Some(vec![
  414                MultiBufferOffsetUtf16(OffsetUtf16(1))..MultiBufferOffsetUtf16(OffsetUtf16(2)),
  415                MultiBufferOffsetUtf16(OffsetUtf16(5))..MultiBufferOffsetUtf16(OffsetUtf16(6)),
  416                MultiBufferOffsetUtf16(OffsetUtf16(9))..MultiBufferOffsetUtf16(OffsetUtf16(10))
  417            ])
  418        );
  419
  420        // Finalize IME composition with multiple cursors.
  421        editor.replace_text_in_range(Some(9..10), "2", window, cx);
  422        assert_eq!(editor.text(cx), "X2ZbX2ZdX2Z");
  423        assert_eq!(editor.marked_text_ranges(cx), None);
  424
  425        editor
  426    });
  427}
  428
  429#[gpui::test]
  430fn test_selection_with_mouse(cx: &mut TestAppContext) {
  431    init_test(cx, |_| {});
  432
  433    let editor = cx.add_window(|window, cx| {
  434        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  435        build_editor(buffer, window, cx)
  436    });
  437
  438    _ = editor.update(cx, |editor, window, cx| {
  439        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  440    });
  441    assert_eq!(
  442        editor
  443            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  444            .unwrap(),
  445        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  446    );
  447
  448    _ = editor.update(cx, |editor, window, cx| {
  449        editor.update_selection(
  450            DisplayPoint::new(DisplayRow(3), 3),
  451            0,
  452            gpui::Point::<f32>::default(),
  453            window,
  454            cx,
  455        );
  456    });
  457
  458    assert_eq!(
  459        editor
  460            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  461            .unwrap(),
  462        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  463    );
  464
  465    _ = editor.update(cx, |editor, window, cx| {
  466        editor.update_selection(
  467            DisplayPoint::new(DisplayRow(1), 1),
  468            0,
  469            gpui::Point::<f32>::default(),
  470            window,
  471            cx,
  472        );
  473    });
  474
  475    assert_eq!(
  476        editor
  477            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  478            .unwrap(),
  479        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  480    );
  481
  482    _ = editor.update(cx, |editor, window, cx| {
  483        editor.end_selection(window, cx);
  484        editor.update_selection(
  485            DisplayPoint::new(DisplayRow(3), 3),
  486            0,
  487            gpui::Point::<f32>::default(),
  488            window,
  489            cx,
  490        );
  491    });
  492
  493    assert_eq!(
  494        editor
  495            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  496            .unwrap(),
  497        [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1)]
  498    );
  499
  500    _ = editor.update(cx, |editor, window, cx| {
  501        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 3), true, 1, window, cx);
  502        editor.update_selection(
  503            DisplayPoint::new(DisplayRow(0), 0),
  504            0,
  505            gpui::Point::<f32>::default(),
  506            window,
  507            cx,
  508        );
  509    });
  510
  511    assert_eq!(
  512        editor
  513            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  514            .unwrap(),
  515        [
  516            DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(1), 1),
  517            DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)
  518        ]
  519    );
  520
  521    _ = editor.update(cx, |editor, window, cx| {
  522        editor.end_selection(window, cx);
  523    });
  524
  525    assert_eq!(
  526        editor
  527            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  528            .unwrap(),
  529        [DisplayPoint::new(DisplayRow(3), 3)..DisplayPoint::new(DisplayRow(0), 0)]
  530    );
  531}
  532
  533#[gpui::test]
  534fn test_multiple_cursor_removal(cx: &mut TestAppContext) {
  535    init_test(cx, |_| {});
  536
  537    let editor = cx.add_window(|window, cx| {
  538        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
  539        build_editor(buffer, window, cx)
  540    });
  541
  542    _ = editor.update(cx, |editor, window, cx| {
  543        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), false, 1, window, cx);
  544    });
  545
  546    _ = editor.update(cx, |editor, window, cx| {
  547        editor.end_selection(window, cx);
  548    });
  549
  550    _ = editor.update(cx, |editor, window, cx| {
  551        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 2), true, 1, window, cx);
  552    });
  553
  554    _ = editor.update(cx, |editor, window, cx| {
  555        editor.end_selection(window, cx);
  556    });
  557
  558    assert_eq!(
  559        editor
  560            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  561            .unwrap(),
  562        [
  563            DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
  564            DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)
  565        ]
  566    );
  567
  568    _ = editor.update(cx, |editor, window, cx| {
  569        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 1), true, 1, window, cx);
  570    });
  571
  572    _ = editor.update(cx, |editor, window, cx| {
  573        editor.end_selection(window, cx);
  574    });
  575
  576    assert_eq!(
  577        editor
  578            .update(cx, |editor, _, cx| display_ranges(editor, cx))
  579            .unwrap(),
  580        [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  581    );
  582}
  583
  584#[gpui::test]
  585fn test_canceling_pending_selection(cx: &mut TestAppContext) {
  586    init_test(cx, |_| {});
  587
  588    let editor = cx.add_window(|window, cx| {
  589        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  590        build_editor(buffer, window, cx)
  591    });
  592
  593    _ = editor.update(cx, |editor, window, cx| {
  594        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  595        assert_eq!(
  596            display_ranges(editor, cx),
  597            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  598        );
  599    });
  600
  601    _ = editor.update(cx, |editor, window, cx| {
  602        editor.update_selection(
  603            DisplayPoint::new(DisplayRow(3), 3),
  604            0,
  605            gpui::Point::<f32>::default(),
  606            window,
  607            cx,
  608        );
  609        assert_eq!(
  610            display_ranges(editor, cx),
  611            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  612        );
  613    });
  614
  615    _ = editor.update(cx, |editor, window, cx| {
  616        editor.cancel(&Cancel, window, cx);
  617        editor.update_selection(
  618            DisplayPoint::new(DisplayRow(1), 1),
  619            0,
  620            gpui::Point::<f32>::default(),
  621            window,
  622            cx,
  623        );
  624        assert_eq!(
  625            display_ranges(editor, cx),
  626            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3)]
  627        );
  628    });
  629}
  630
  631#[gpui::test]
  632fn test_movement_actions_with_pending_selection(cx: &mut TestAppContext) {
  633    init_test(cx, |_| {});
  634
  635    let editor = cx.add_window(|window, cx| {
  636        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  637        build_editor(buffer, window, cx)
  638    });
  639
  640    _ = editor.update(cx, |editor, window, cx| {
  641        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  642        assert_eq!(
  643            display_ranges(editor, cx),
  644            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  645        );
  646
  647        editor.move_down(&Default::default(), window, cx);
  648        assert_eq!(
  649            display_ranges(editor, cx),
  650            [DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 2)]
  651        );
  652
  653        editor.begin_selection(DisplayPoint::new(DisplayRow(2), 2), false, 1, window, cx);
  654        assert_eq!(
  655            display_ranges(editor, cx),
  656            [DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)]
  657        );
  658
  659        editor.move_up(&Default::default(), window, cx);
  660        assert_eq!(
  661            display_ranges(editor, cx),
  662            [DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2)]
  663        );
  664    });
  665}
  666
  667#[gpui::test]
  668fn test_extending_selection(cx: &mut TestAppContext) {
  669    init_test(cx, |_| {});
  670
  671    let editor = cx.add_window(|window, cx| {
  672        let buffer = MultiBuffer::build_simple("aaa bbb ccc ddd eee", cx);
  673        build_editor(buffer, window, cx)
  674    });
  675
  676    _ = editor.update(cx, |editor, window, cx| {
  677        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), false, 1, window, cx);
  678        editor.end_selection(window, cx);
  679        assert_eq!(
  680            display_ranges(editor, cx),
  681            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)]
  682        );
  683
  684        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  685        editor.end_selection(window, cx);
  686        assert_eq!(
  687            display_ranges(editor, cx),
  688            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 10)]
  689        );
  690
  691        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  692        editor.end_selection(window, cx);
  693        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 2, window, cx);
  694        assert_eq!(
  695            display_ranges(editor, cx),
  696            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 11)]
  697        );
  698
  699        editor.update_selection(
  700            DisplayPoint::new(DisplayRow(0), 1),
  701            0,
  702            gpui::Point::<f32>::default(),
  703            window,
  704            cx,
  705        );
  706        editor.end_selection(window, cx);
  707        assert_eq!(
  708            display_ranges(editor, cx),
  709            [DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 0)]
  710        );
  711
  712        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 1, window, cx);
  713        editor.end_selection(window, cx);
  714        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 5), true, 2, window, cx);
  715        editor.end_selection(window, cx);
  716        assert_eq!(
  717            display_ranges(editor, cx),
  718            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  719        );
  720
  721        editor.extend_selection(DisplayPoint::new(DisplayRow(0), 10), 1, window, cx);
  722        assert_eq!(
  723            display_ranges(editor, cx),
  724            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 11)]
  725        );
  726
  727        editor.update_selection(
  728            DisplayPoint::new(DisplayRow(0), 6),
  729            0,
  730            gpui::Point::<f32>::default(),
  731            window,
  732            cx,
  733        );
  734        assert_eq!(
  735            display_ranges(editor, cx),
  736            [DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 7)]
  737        );
  738
  739        editor.update_selection(
  740            DisplayPoint::new(DisplayRow(0), 1),
  741            0,
  742            gpui::Point::<f32>::default(),
  743            window,
  744            cx,
  745        );
  746        editor.end_selection(window, cx);
  747        assert_eq!(
  748            display_ranges(editor, cx),
  749            [DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 0)]
  750        );
  751    });
  752}
  753
  754#[gpui::test]
  755fn test_clone(cx: &mut TestAppContext) {
  756    init_test(cx, |_| {});
  757
  758    let (text, selection_ranges) = marked_text_ranges(
  759        indoc! {"
  760            one
  761            two
  762            threeˇ
  763            four
  764            fiveˇ
  765        "},
  766        true,
  767    );
  768
  769    let editor = cx.add_window(|window, cx| {
  770        let buffer = MultiBuffer::build_simple(&text, cx);
  771        build_editor(buffer, window, cx)
  772    });
  773
  774    _ = editor.update(cx, |editor, window, cx| {
  775        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  776            s.select_ranges(
  777                selection_ranges
  778                    .iter()
  779                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
  780            )
  781        });
  782        editor.fold_creases(
  783            vec![
  784                Crease::simple(Point::new(1, 0)..Point::new(2, 0), FoldPlaceholder::test()),
  785                Crease::simple(Point::new(3, 0)..Point::new(4, 0), FoldPlaceholder::test()),
  786            ],
  787            true,
  788            window,
  789            cx,
  790        );
  791    });
  792
  793    let cloned_editor = editor
  794        .update(cx, |editor, _, cx| {
  795            cx.open_window(Default::default(), |window, cx| {
  796                cx.new(|cx| editor.clone(window, cx))
  797            })
  798        })
  799        .unwrap()
  800        .unwrap();
  801
  802    let snapshot = editor
  803        .update(cx, |e, window, cx| e.snapshot(window, cx))
  804        .unwrap();
  805    let cloned_snapshot = cloned_editor
  806        .update(cx, |e, window, cx| e.snapshot(window, cx))
  807        .unwrap();
  808
  809    assert_eq!(
  810        cloned_editor
  811            .update(cx, |e, _, cx| e.display_text(cx))
  812            .unwrap(),
  813        editor.update(cx, |e, _, cx| e.display_text(cx)).unwrap()
  814    );
  815    assert_eq!(
  816        cloned_snapshot
  817            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  818            .collect::<Vec<_>>(),
  819        snapshot
  820            .folds_in_range(MultiBufferOffset(0)..MultiBufferOffset(text.len()))
  821            .collect::<Vec<_>>(),
  822    );
  823    assert_set_eq!(
  824        cloned_editor
  825            .update(cx, |editor, _, cx| editor
  826                .selections
  827                .ranges::<Point>(&editor.display_snapshot(cx)))
  828            .unwrap(),
  829        editor
  830            .update(cx, |editor, _, cx| editor
  831                .selections
  832                .ranges(&editor.display_snapshot(cx)))
  833            .unwrap()
  834    );
  835    assert_set_eq!(
  836        cloned_editor
  837            .update(cx, |e, _window, cx| e
  838                .selections
  839                .display_ranges(&e.display_snapshot(cx)))
  840            .unwrap(),
  841        editor
  842            .update(cx, |e, _, cx| e
  843                .selections
  844                .display_ranges(&e.display_snapshot(cx)))
  845            .unwrap()
  846    );
  847}
  848
  849#[gpui::test]
  850async fn test_navigation_history(cx: &mut TestAppContext) {
  851    init_test(cx, |_| {});
  852
  853    use workspace::item::Item;
  854
  855    let fs = FakeFs::new(cx.executor());
  856    let project = Project::test(fs, [], cx).await;
  857    let workspace = cx.add_window(|window, cx| Workspace::test_new(project, window, cx));
  858    let pane = workspace
  859        .update(cx, |workspace, _, _| workspace.active_pane().clone())
  860        .unwrap();
  861
  862    _ = workspace.update(cx, |_v, window, cx| {
  863        cx.new(|cx| {
  864            let buffer = MultiBuffer::build_simple(&sample_text(300, 5, 'a'), cx);
  865            let mut editor = build_editor(buffer, window, cx);
  866            let handle = cx.entity();
  867            editor.set_nav_history(Some(pane.read(cx).nav_history_for_item(&handle)));
  868
  869            fn pop_history(editor: &mut Editor, cx: &mut App) -> Option<NavigationEntry> {
  870                editor.nav_history.as_mut().unwrap().pop_backward(cx)
  871            }
  872
  873            // Move the cursor a small distance.
  874            // Nothing is added to the navigation history.
  875            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  876                s.select_display_ranges([
  877                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
  878                ])
  879            });
  880            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  881                s.select_display_ranges([
  882                    DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)
  883                ])
  884            });
  885            assert!(pop_history(&mut editor, cx).is_none());
  886
  887            // Move the cursor a large distance.
  888            // The history can jump back to the previous position.
  889            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
  890                s.select_display_ranges([
  891                    DisplayPoint::new(DisplayRow(13), 0)..DisplayPoint::new(DisplayRow(13), 3)
  892                ])
  893            });
  894            let nav_entry = pop_history(&mut editor, cx).unwrap();
  895            editor.navigate(nav_entry.data.unwrap(), window, cx);
  896            assert_eq!(nav_entry.item.id(), cx.entity_id());
  897            assert_eq!(
  898                editor
  899                    .selections
  900                    .display_ranges(&editor.display_snapshot(cx)),
  901                &[DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0)]
  902            );
  903            assert!(pop_history(&mut editor, cx).is_none());
  904
  905            // Move the cursor a small distance via the mouse.
  906            // Nothing is added to the navigation history.
  907            editor.begin_selection(DisplayPoint::new(DisplayRow(5), 0), false, 1, window, cx);
  908            editor.end_selection(window, cx);
  909            assert_eq!(
  910                editor
  911                    .selections
  912                    .display_ranges(&editor.display_snapshot(cx)),
  913                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  914            );
  915            assert!(pop_history(&mut editor, cx).is_none());
  916
  917            // Move the cursor a large distance via the mouse.
  918            // The history can jump back to the previous position.
  919            editor.begin_selection(DisplayPoint::new(DisplayRow(15), 0), false, 1, window, cx);
  920            editor.end_selection(window, cx);
  921            assert_eq!(
  922                editor
  923                    .selections
  924                    .display_ranges(&editor.display_snapshot(cx)),
  925                &[DisplayPoint::new(DisplayRow(15), 0)..DisplayPoint::new(DisplayRow(15), 0)]
  926            );
  927            let nav_entry = pop_history(&mut editor, cx).unwrap();
  928            editor.navigate(nav_entry.data.unwrap(), window, cx);
  929            assert_eq!(nav_entry.item.id(), cx.entity_id());
  930            assert_eq!(
  931                editor
  932                    .selections
  933                    .display_ranges(&editor.display_snapshot(cx)),
  934                &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0)]
  935            );
  936            assert!(pop_history(&mut editor, cx).is_none());
  937
  938            // Set scroll position to check later
  939            editor.set_scroll_position(gpui::Point::<f64>::new(5.5, 5.5), window, cx);
  940            let original_scroll_position = editor.scroll_manager.anchor();
  941
  942            // Jump to the end of the document and adjust scroll
  943            editor.move_to_end(&MoveToEnd, window, cx);
  944            editor.set_scroll_position(gpui::Point::<f64>::new(-2.5, -0.5), window, cx);
  945            assert_ne!(editor.scroll_manager.anchor(), original_scroll_position);
  946
  947            let nav_entry = pop_history(&mut editor, cx).unwrap();
  948            editor.navigate(nav_entry.data.unwrap(), window, cx);
  949            assert_eq!(editor.scroll_manager.anchor(), original_scroll_position);
  950
  951            // Ensure we don't panic when navigation data contains invalid anchors *and* points.
  952            let mut invalid_anchor = editor.scroll_manager.anchor().anchor;
  953            invalid_anchor.text_anchor.buffer_id = BufferId::new(999).ok();
  954            let invalid_point = Point::new(9999, 0);
  955            editor.navigate(
  956                Box::new(NavigationData {
  957                    cursor_anchor: invalid_anchor,
  958                    cursor_position: invalid_point,
  959                    scroll_anchor: ScrollAnchor {
  960                        anchor: invalid_anchor,
  961                        offset: Default::default(),
  962                    },
  963                    scroll_top_row: invalid_point.row,
  964                }),
  965                window,
  966                cx,
  967            );
  968            assert_eq!(
  969                editor
  970                    .selections
  971                    .display_ranges(&editor.display_snapshot(cx)),
  972                &[editor.max_point(cx)..editor.max_point(cx)]
  973            );
  974            assert_eq!(
  975                editor.scroll_position(cx),
  976                gpui::Point::new(0., editor.max_point(cx).row().as_f64())
  977            );
  978
  979            editor
  980        })
  981    });
  982}
  983
  984#[gpui::test]
  985fn test_cancel(cx: &mut TestAppContext) {
  986    init_test(cx, |_| {});
  987
  988    let editor = cx.add_window(|window, cx| {
  989        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\ndddddd\n", cx);
  990        build_editor(buffer, window, cx)
  991    });
  992
  993    _ = editor.update(cx, |editor, window, cx| {
  994        editor.begin_selection(DisplayPoint::new(DisplayRow(3), 4), false, 1, window, cx);
  995        editor.update_selection(
  996            DisplayPoint::new(DisplayRow(1), 1),
  997            0,
  998            gpui::Point::<f32>::default(),
  999            window,
 1000            cx,
 1001        );
 1002        editor.end_selection(window, cx);
 1003
 1004        editor.begin_selection(DisplayPoint::new(DisplayRow(0), 1), true, 1, window, cx);
 1005        editor.update_selection(
 1006            DisplayPoint::new(DisplayRow(0), 3),
 1007            0,
 1008            gpui::Point::<f32>::default(),
 1009            window,
 1010            cx,
 1011        );
 1012        editor.end_selection(window, cx);
 1013        assert_eq!(
 1014            display_ranges(editor, cx),
 1015            [
 1016                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 3),
 1017                DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1),
 1018            ]
 1019        );
 1020    });
 1021
 1022    _ = editor.update(cx, |editor, window, cx| {
 1023        editor.cancel(&Cancel, window, cx);
 1024        assert_eq!(
 1025            display_ranges(editor, cx),
 1026            [DisplayPoint::new(DisplayRow(3), 4)..DisplayPoint::new(DisplayRow(1), 1)]
 1027        );
 1028    });
 1029
 1030    _ = editor.update(cx, |editor, window, cx| {
 1031        editor.cancel(&Cancel, window, cx);
 1032        assert_eq!(
 1033            display_ranges(editor, cx),
 1034            [DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1)]
 1035        );
 1036    });
 1037}
 1038
 1039#[gpui::test]
 1040fn test_fold_action(cx: &mut TestAppContext) {
 1041    init_test(cx, |_| {});
 1042
 1043    let editor = cx.add_window(|window, cx| {
 1044        let buffer = MultiBuffer::build_simple(
 1045            &"
 1046                impl Foo {
 1047                    // Hello!
 1048
 1049                    fn a() {
 1050                        1
 1051                    }
 1052
 1053                    fn b() {
 1054                        2
 1055                    }
 1056
 1057                    fn c() {
 1058                        3
 1059                    }
 1060                }
 1061            "
 1062            .unindent(),
 1063            cx,
 1064        );
 1065        build_editor(buffer, window, cx)
 1066    });
 1067
 1068    _ = editor.update(cx, |editor, window, cx| {
 1069        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1070            s.select_display_ranges([
 1071                DisplayPoint::new(DisplayRow(7), 0)..DisplayPoint::new(DisplayRow(12), 0)
 1072            ]);
 1073        });
 1074        editor.fold(&Fold, window, cx);
 1075        assert_eq!(
 1076            editor.display_text(cx),
 1077            "
 1078                impl Foo {
 1079                    // Hello!
 1080
 1081                    fn a() {
 1082                        1
 1083                    }
 1084
 1085                    fn b() {⋯
 1086                    }
 1087
 1088                    fn c() {⋯
 1089                    }
 1090                }
 1091            "
 1092            .unindent(),
 1093        );
 1094
 1095        editor.fold(&Fold, window, cx);
 1096        assert_eq!(
 1097            editor.display_text(cx),
 1098            "
 1099                impl Foo {⋯
 1100                }
 1101            "
 1102            .unindent(),
 1103        );
 1104
 1105        editor.unfold_lines(&UnfoldLines, window, cx);
 1106        assert_eq!(
 1107            editor.display_text(cx),
 1108            "
 1109                impl Foo {
 1110                    // Hello!
 1111
 1112                    fn a() {
 1113                        1
 1114                    }
 1115
 1116                    fn b() {⋯
 1117                    }
 1118
 1119                    fn c() {⋯
 1120                    }
 1121                }
 1122            "
 1123            .unindent(),
 1124        );
 1125
 1126        editor.unfold_lines(&UnfoldLines, window, cx);
 1127        assert_eq!(
 1128            editor.display_text(cx),
 1129            editor.buffer.read(cx).read(cx).text()
 1130        );
 1131    });
 1132}
 1133
 1134#[gpui::test]
 1135fn test_fold_action_whitespace_sensitive_language(cx: &mut TestAppContext) {
 1136    init_test(cx, |_| {});
 1137
 1138    let editor = cx.add_window(|window, cx| {
 1139        let buffer = MultiBuffer::build_simple(
 1140            &"
 1141                class Foo:
 1142                    # Hello!
 1143
 1144                    def a():
 1145                        print(1)
 1146
 1147                    def b():
 1148                        print(2)
 1149
 1150                    def c():
 1151                        print(3)
 1152            "
 1153            .unindent(),
 1154            cx,
 1155        );
 1156        build_editor(buffer, window, cx)
 1157    });
 1158
 1159    _ = editor.update(cx, |editor, window, cx| {
 1160        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1161            s.select_display_ranges([
 1162                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(10), 0)
 1163            ]);
 1164        });
 1165        editor.fold(&Fold, window, cx);
 1166        assert_eq!(
 1167            editor.display_text(cx),
 1168            "
 1169                class Foo:
 1170                    # Hello!
 1171
 1172                    def a():
 1173                        print(1)
 1174
 1175                    def b():⋯
 1176
 1177                    def c():⋯
 1178            "
 1179            .unindent(),
 1180        );
 1181
 1182        editor.fold(&Fold, window, cx);
 1183        assert_eq!(
 1184            editor.display_text(cx),
 1185            "
 1186                class Foo:⋯
 1187            "
 1188            .unindent(),
 1189        );
 1190
 1191        editor.unfold_lines(&UnfoldLines, window, cx);
 1192        assert_eq!(
 1193            editor.display_text(cx),
 1194            "
 1195                class Foo:
 1196                    # Hello!
 1197
 1198                    def a():
 1199                        print(1)
 1200
 1201                    def b():⋯
 1202
 1203                    def c():⋯
 1204            "
 1205            .unindent(),
 1206        );
 1207
 1208        editor.unfold_lines(&UnfoldLines, window, cx);
 1209        assert_eq!(
 1210            editor.display_text(cx),
 1211            editor.buffer.read(cx).read(cx).text()
 1212        );
 1213    });
 1214}
 1215
 1216#[gpui::test]
 1217fn test_fold_action_multiple_line_breaks(cx: &mut TestAppContext) {
 1218    init_test(cx, |_| {});
 1219
 1220    let editor = cx.add_window(|window, cx| {
 1221        let buffer = MultiBuffer::build_simple(
 1222            &"
 1223                class Foo:
 1224                    # Hello!
 1225
 1226                    def a():
 1227                        print(1)
 1228
 1229                    def b():
 1230                        print(2)
 1231
 1232
 1233                    def c():
 1234                        print(3)
 1235
 1236
 1237            "
 1238            .unindent(),
 1239            cx,
 1240        );
 1241        build_editor(buffer, window, cx)
 1242    });
 1243
 1244    _ = editor.update(cx, |editor, window, cx| {
 1245        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1246            s.select_display_ranges([
 1247                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(11), 0)
 1248            ]);
 1249        });
 1250        editor.fold(&Fold, window, cx);
 1251        assert_eq!(
 1252            editor.display_text(cx),
 1253            "
 1254                class Foo:
 1255                    # Hello!
 1256
 1257                    def a():
 1258                        print(1)
 1259
 1260                    def b():⋯
 1261
 1262
 1263                    def c():⋯
 1264
 1265
 1266            "
 1267            .unindent(),
 1268        );
 1269
 1270        editor.fold(&Fold, window, cx);
 1271        assert_eq!(
 1272            editor.display_text(cx),
 1273            "
 1274                class Foo:⋯
 1275
 1276
 1277            "
 1278            .unindent(),
 1279        );
 1280
 1281        editor.unfold_lines(&UnfoldLines, window, cx);
 1282        assert_eq!(
 1283            editor.display_text(cx),
 1284            "
 1285                class Foo:
 1286                    # Hello!
 1287
 1288                    def a():
 1289                        print(1)
 1290
 1291                    def b():⋯
 1292
 1293
 1294                    def c():⋯
 1295
 1296
 1297            "
 1298            .unindent(),
 1299        );
 1300
 1301        editor.unfold_lines(&UnfoldLines, window, cx);
 1302        assert_eq!(
 1303            editor.display_text(cx),
 1304            editor.buffer.read(cx).read(cx).text()
 1305        );
 1306    });
 1307}
 1308
 1309#[gpui::test]
 1310fn test_fold_at_level(cx: &mut TestAppContext) {
 1311    init_test(cx, |_| {});
 1312
 1313    let editor = cx.add_window(|window, cx| {
 1314        let buffer = MultiBuffer::build_simple(
 1315            &"
 1316                class Foo:
 1317                    # Hello!
 1318
 1319                    def a():
 1320                        print(1)
 1321
 1322                    def b():
 1323                        print(2)
 1324
 1325
 1326                class Bar:
 1327                    # World!
 1328
 1329                    def a():
 1330                        print(1)
 1331
 1332                    def b():
 1333                        print(2)
 1334
 1335
 1336            "
 1337            .unindent(),
 1338            cx,
 1339        );
 1340        build_editor(buffer, window, cx)
 1341    });
 1342
 1343    _ = editor.update(cx, |editor, window, cx| {
 1344        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1345        assert_eq!(
 1346            editor.display_text(cx),
 1347            "
 1348                class Foo:
 1349                    # Hello!
 1350
 1351                    def a():⋯
 1352
 1353                    def b():⋯
 1354
 1355
 1356                class Bar:
 1357                    # World!
 1358
 1359                    def a():⋯
 1360
 1361                    def b():⋯
 1362
 1363
 1364            "
 1365            .unindent(),
 1366        );
 1367
 1368        editor.fold_at_level(&FoldAtLevel(1), window, cx);
 1369        assert_eq!(
 1370            editor.display_text(cx),
 1371            "
 1372                class Foo:⋯
 1373
 1374
 1375                class Bar:⋯
 1376
 1377
 1378            "
 1379            .unindent(),
 1380        );
 1381
 1382        editor.unfold_all(&UnfoldAll, window, cx);
 1383        editor.fold_at_level(&FoldAtLevel(0), window, cx);
 1384        assert_eq!(
 1385            editor.display_text(cx),
 1386            "
 1387                class Foo:
 1388                    # Hello!
 1389
 1390                    def a():
 1391                        print(1)
 1392
 1393                    def b():
 1394                        print(2)
 1395
 1396
 1397                class Bar:
 1398                    # World!
 1399
 1400                    def a():
 1401                        print(1)
 1402
 1403                    def b():
 1404                        print(2)
 1405
 1406
 1407            "
 1408            .unindent(),
 1409        );
 1410
 1411        assert_eq!(
 1412            editor.display_text(cx),
 1413            editor.buffer.read(cx).read(cx).text()
 1414        );
 1415        let (_, positions) = marked_text_ranges(
 1416            &"
 1417                       class Foo:
 1418                           # Hello!
 1419
 1420                           def a():
 1421                              print(1)
 1422
 1423                           def b():
 1424                               p«riˇ»nt(2)
 1425
 1426
 1427                       class Bar:
 1428                           # World!
 1429
 1430                           def a():
 1431                               «ˇprint(1)
 1432
 1433                           def b():
 1434                               print(2)»
 1435
 1436
 1437                   "
 1438            .unindent(),
 1439            true,
 1440        );
 1441
 1442        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 1443            s.select_ranges(
 1444                positions
 1445                    .iter()
 1446                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
 1447            )
 1448        });
 1449
 1450        editor.fold_at_level(&FoldAtLevel(2), window, cx);
 1451        assert_eq!(
 1452            editor.display_text(cx),
 1453            "
 1454                class Foo:
 1455                    # Hello!
 1456
 1457                    def a():⋯
 1458
 1459                    def b():
 1460                        print(2)
 1461
 1462
 1463                class Bar:
 1464                    # World!
 1465
 1466                    def a():
 1467                        print(1)
 1468
 1469                    def b():
 1470                        print(2)
 1471
 1472
 1473            "
 1474            .unindent(),
 1475        );
 1476    });
 1477}
 1478
 1479#[gpui::test]
 1480fn test_move_cursor(cx: &mut TestAppContext) {
 1481    init_test(cx, |_| {});
 1482
 1483    let buffer = cx.update(|cx| MultiBuffer::build_simple(&sample_text(6, 6, 'a'), cx));
 1484    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
 1485
 1486    buffer.update(cx, |buffer, cx| {
 1487        buffer.edit(
 1488            vec![
 1489                (Point::new(1, 0)..Point::new(1, 0), "\t"),
 1490                (Point::new(1, 1)..Point::new(1, 1), "\t"),
 1491            ],
 1492            None,
 1493            cx,
 1494        );
 1495    });
 1496    _ = editor.update(cx, |editor, window, cx| {
 1497        assert_eq!(
 1498            display_ranges(editor, cx),
 1499            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1500        );
 1501
 1502        editor.move_down(&MoveDown, window, cx);
 1503        assert_eq!(
 1504            display_ranges(editor, cx),
 1505            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1506        );
 1507
 1508        editor.move_right(&MoveRight, window, cx);
 1509        assert_eq!(
 1510            display_ranges(editor, cx),
 1511            &[DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4)]
 1512        );
 1513
 1514        editor.move_left(&MoveLeft, window, cx);
 1515        assert_eq!(
 1516            display_ranges(editor, cx),
 1517            &[DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)]
 1518        );
 1519
 1520        editor.move_up(&MoveUp, window, cx);
 1521        assert_eq!(
 1522            display_ranges(editor, cx),
 1523            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1524        );
 1525
 1526        editor.move_to_end(&MoveToEnd, window, cx);
 1527        assert_eq!(
 1528            display_ranges(editor, cx),
 1529            &[DisplayPoint::new(DisplayRow(5), 6)..DisplayPoint::new(DisplayRow(5), 6)]
 1530        );
 1531
 1532        editor.move_to_beginning(&MoveToBeginning, window, cx);
 1533        assert_eq!(
 1534            display_ranges(editor, cx),
 1535            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 1536        );
 1537
 1538        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1539            s.select_display_ranges([
 1540                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 2)
 1541            ]);
 1542        });
 1543        editor.select_to_beginning(&SelectToBeginning, window, cx);
 1544        assert_eq!(
 1545            display_ranges(editor, cx),
 1546            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 0)]
 1547        );
 1548
 1549        editor.select_to_end(&SelectToEnd, window, cx);
 1550        assert_eq!(
 1551            display_ranges(editor, cx),
 1552            &[DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(5), 6)]
 1553        );
 1554    });
 1555}
 1556
 1557#[gpui::test]
 1558fn test_move_cursor_multibyte(cx: &mut TestAppContext) {
 1559    init_test(cx, |_| {});
 1560
 1561    let editor = cx.add_window(|window, cx| {
 1562        let buffer = MultiBuffer::build_simple("🟥🟧🟨🟩🟦🟪\nabcde\nαβγδε", cx);
 1563        build_editor(buffer, window, cx)
 1564    });
 1565
 1566    assert_eq!('🟥'.len_utf8(), 4);
 1567    assert_eq!('α'.len_utf8(), 2);
 1568
 1569    _ = editor.update(cx, |editor, window, cx| {
 1570        editor.fold_creases(
 1571            vec![
 1572                Crease::simple(Point::new(0, 8)..Point::new(0, 16), FoldPlaceholder::test()),
 1573                Crease::simple(Point::new(1, 2)..Point::new(1, 4), FoldPlaceholder::test()),
 1574                Crease::simple(Point::new(2, 4)..Point::new(2, 8), FoldPlaceholder::test()),
 1575            ],
 1576            true,
 1577            window,
 1578            cx,
 1579        );
 1580        assert_eq!(editor.display_text(cx), "🟥🟧⋯🟦🟪\nab⋯e\nαβ⋯ε");
 1581
 1582        editor.move_right(&MoveRight, window, cx);
 1583        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1584        editor.move_right(&MoveRight, window, cx);
 1585        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1586        editor.move_right(&MoveRight, window, cx);
 1587        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧⋯".len())]);
 1588
 1589        editor.move_down(&MoveDown, window, cx);
 1590        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1591        editor.move_left(&MoveLeft, window, cx);
 1592        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯".len())]);
 1593        editor.move_left(&MoveLeft, window, cx);
 1594        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab".len())]);
 1595        editor.move_left(&MoveLeft, window, cx);
 1596        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "a".len())]);
 1597
 1598        editor.move_down(&MoveDown, window, cx);
 1599        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "α".len())]);
 1600        editor.move_right(&MoveRight, window, cx);
 1601        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ".len())]);
 1602        editor.move_right(&MoveRight, window, cx);
 1603        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯".len())]);
 1604        editor.move_right(&MoveRight, window, cx);
 1605        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1606
 1607        editor.move_up(&MoveUp, window, cx);
 1608        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1609        editor.move_down(&MoveDown, window, cx);
 1610        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβ⋯ε".len())]);
 1611        editor.move_up(&MoveUp, window, cx);
 1612        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "ab⋯e".len())]);
 1613
 1614        editor.move_up(&MoveUp, window, cx);
 1615        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥🟧".len())]);
 1616        editor.move_left(&MoveLeft, window, cx);
 1617        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "🟥".len())]);
 1618        editor.move_left(&MoveLeft, window, cx);
 1619        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1620    });
 1621}
 1622
 1623#[gpui::test]
 1624fn test_move_cursor_different_line_lengths(cx: &mut TestAppContext) {
 1625    init_test(cx, |_| {});
 1626
 1627    let editor = cx.add_window(|window, cx| {
 1628        let buffer = MultiBuffer::build_simple("ⓐⓑⓒⓓⓔ\nabcd\nαβγ\nabcd\nⓐⓑⓒⓓⓔ\n", cx);
 1629        build_editor(buffer, window, cx)
 1630    });
 1631    _ = editor.update(cx, |editor, window, cx| {
 1632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1633            s.select_display_ranges([empty_range(0, "ⓐⓑⓒⓓⓔ".len())]);
 1634        });
 1635
 1636        // moving above start of document should move selection to start of document,
 1637        // but the next move down should still be at the original goal_x
 1638        editor.move_up(&MoveUp, window, cx);
 1639        assert_eq!(display_ranges(editor, cx), &[empty_range(0, "".len())]);
 1640
 1641        editor.move_down(&MoveDown, window, cx);
 1642        assert_eq!(display_ranges(editor, cx), &[empty_range(1, "abcd".len())]);
 1643
 1644        editor.move_down(&MoveDown, window, cx);
 1645        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1646
 1647        editor.move_down(&MoveDown, window, cx);
 1648        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1649
 1650        editor.move_down(&MoveDown, window, cx);
 1651        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1652
 1653        // moving past end of document should not change goal_x
 1654        editor.move_down(&MoveDown, window, cx);
 1655        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1656
 1657        editor.move_down(&MoveDown, window, cx);
 1658        assert_eq!(display_ranges(editor, cx), &[empty_range(5, "".len())]);
 1659
 1660        editor.move_up(&MoveUp, window, cx);
 1661        assert_eq!(display_ranges(editor, cx), &[empty_range(4, "ⓐⓑⓒⓓⓔ".len())]);
 1662
 1663        editor.move_up(&MoveUp, window, cx);
 1664        assert_eq!(display_ranges(editor, cx), &[empty_range(3, "abcd".len())]);
 1665
 1666        editor.move_up(&MoveUp, window, cx);
 1667        assert_eq!(display_ranges(editor, cx), &[empty_range(2, "αβγ".len())]);
 1668    });
 1669}
 1670
 1671#[gpui::test]
 1672fn test_beginning_end_of_line(cx: &mut TestAppContext) {
 1673    init_test(cx, |_| {});
 1674    let move_to_beg = MoveToBeginningOfLine {
 1675        stop_at_soft_wraps: true,
 1676        stop_at_indent: true,
 1677    };
 1678
 1679    let delete_to_beg = DeleteToBeginningOfLine {
 1680        stop_at_indent: false,
 1681    };
 1682
 1683    let move_to_end = MoveToEndOfLine {
 1684        stop_at_soft_wraps: true,
 1685    };
 1686
 1687    let editor = cx.add_window(|window, cx| {
 1688        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1689        build_editor(buffer, window, cx)
 1690    });
 1691    _ = editor.update(cx, |editor, window, cx| {
 1692        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1693            s.select_display_ranges([
 1694                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1695                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1696            ]);
 1697        });
 1698    });
 1699
 1700    _ = editor.update(cx, |editor, window, cx| {
 1701        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1702        assert_eq!(
 1703            display_ranges(editor, cx),
 1704            &[
 1705                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1706                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1707            ]
 1708        );
 1709    });
 1710
 1711    _ = editor.update(cx, |editor, window, cx| {
 1712        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1713        assert_eq!(
 1714            display_ranges(editor, cx),
 1715            &[
 1716                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1717                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1718            ]
 1719        );
 1720    });
 1721
 1722    _ = editor.update(cx, |editor, window, cx| {
 1723        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1724        assert_eq!(
 1725            display_ranges(editor, cx),
 1726            &[
 1727                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1728                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1729            ]
 1730        );
 1731    });
 1732
 1733    _ = editor.update(cx, |editor, window, cx| {
 1734        editor.move_to_end_of_line(&move_to_end, window, cx);
 1735        assert_eq!(
 1736            display_ranges(editor, cx),
 1737            &[
 1738                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1739                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1740            ]
 1741        );
 1742    });
 1743
 1744    // Moving to the end of line again is a no-op.
 1745    _ = editor.update(cx, |editor, window, cx| {
 1746        editor.move_to_end_of_line(&move_to_end, window, cx);
 1747        assert_eq!(
 1748            display_ranges(editor, cx),
 1749            &[
 1750                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3),
 1751                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 1752            ]
 1753        );
 1754    });
 1755
 1756    _ = editor.update(cx, |editor, window, cx| {
 1757        editor.move_left(&MoveLeft, window, cx);
 1758        editor.select_to_beginning_of_line(
 1759            &SelectToBeginningOfLine {
 1760                stop_at_soft_wraps: true,
 1761                stop_at_indent: true,
 1762            },
 1763            window,
 1764            cx,
 1765        );
 1766        assert_eq!(
 1767            display_ranges(editor, cx),
 1768            &[
 1769                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1770                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1771            ]
 1772        );
 1773    });
 1774
 1775    _ = editor.update(cx, |editor, window, cx| {
 1776        editor.select_to_beginning_of_line(
 1777            &SelectToBeginningOfLine {
 1778                stop_at_soft_wraps: true,
 1779                stop_at_indent: true,
 1780            },
 1781            window,
 1782            cx,
 1783        );
 1784        assert_eq!(
 1785            display_ranges(editor, cx),
 1786            &[
 1787                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1788                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 1789            ]
 1790        );
 1791    });
 1792
 1793    _ = editor.update(cx, |editor, window, cx| {
 1794        editor.select_to_beginning_of_line(
 1795            &SelectToBeginningOfLine {
 1796                stop_at_soft_wraps: true,
 1797                stop_at_indent: true,
 1798            },
 1799            window,
 1800            cx,
 1801        );
 1802        assert_eq!(
 1803            display_ranges(editor, cx),
 1804            &[
 1805                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 1806                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 1807            ]
 1808        );
 1809    });
 1810
 1811    _ = editor.update(cx, |editor, window, cx| {
 1812        editor.select_to_end_of_line(
 1813            &SelectToEndOfLine {
 1814                stop_at_soft_wraps: true,
 1815            },
 1816            window,
 1817            cx,
 1818        );
 1819        assert_eq!(
 1820            display_ranges(editor, cx),
 1821            &[
 1822                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
 1823                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 5),
 1824            ]
 1825        );
 1826    });
 1827
 1828    _ = editor.update(cx, |editor, window, cx| {
 1829        editor.delete_to_end_of_line(&DeleteToEndOfLine, window, cx);
 1830        assert_eq!(editor.display_text(cx), "ab\n  de");
 1831        assert_eq!(
 1832            display_ranges(editor, cx),
 1833            &[
 1834                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 1835                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1836            ]
 1837        );
 1838    });
 1839
 1840    _ = editor.update(cx, |editor, window, cx| {
 1841        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 1842        assert_eq!(editor.display_text(cx), "\n");
 1843        assert_eq!(
 1844            display_ranges(editor, cx),
 1845            &[
 1846                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1847                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 1848            ]
 1849        );
 1850    });
 1851}
 1852
 1853#[gpui::test]
 1854fn test_beginning_end_of_line_ignore_soft_wrap(cx: &mut TestAppContext) {
 1855    init_test(cx, |_| {});
 1856    let move_to_beg = MoveToBeginningOfLine {
 1857        stop_at_soft_wraps: false,
 1858        stop_at_indent: false,
 1859    };
 1860
 1861    let move_to_end = MoveToEndOfLine {
 1862        stop_at_soft_wraps: false,
 1863    };
 1864
 1865    let editor = cx.add_window(|window, cx| {
 1866        let buffer = MultiBuffer::build_simple("thequickbrownfox\njumpedoverthelazydogs", cx);
 1867        build_editor(buffer, window, cx)
 1868    });
 1869
 1870    _ = editor.update(cx, |editor, window, cx| {
 1871        editor.set_wrap_width(Some(140.0.into()), cx);
 1872
 1873        // We expect the following lines after wrapping
 1874        // ```
 1875        // thequickbrownfox
 1876        // jumpedoverthelazydo
 1877        // gs
 1878        // ```
 1879        // The final `gs` was soft-wrapped onto a new line.
 1880        assert_eq!(
 1881            "thequickbrownfox\njumpedoverthelaz\nydogs",
 1882            editor.display_text(cx),
 1883        );
 1884
 1885        // First, let's assert behavior on the first line, that was not soft-wrapped.
 1886        // Start the cursor at the `k` on the first line
 1887        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1888            s.select_display_ranges([
 1889                DisplayPoint::new(DisplayRow(0), 7)..DisplayPoint::new(DisplayRow(0), 7)
 1890            ]);
 1891        });
 1892
 1893        // Moving to the beginning of the line should put us at the beginning of the line.
 1894        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1895        assert_eq!(
 1896            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),],
 1897            display_ranges(editor, cx)
 1898        );
 1899
 1900        // Moving to the end of the line should put us at the end of the line.
 1901        editor.move_to_end_of_line(&move_to_end, window, cx);
 1902        assert_eq!(
 1903            vec![DisplayPoint::new(DisplayRow(0), 16)..DisplayPoint::new(DisplayRow(0), 16),],
 1904            display_ranges(editor, cx)
 1905        );
 1906
 1907        // Now, let's assert behavior on the second line, that ended up being soft-wrapped.
 1908        // Start the cursor at the last line (`y` that was wrapped to a new line)
 1909        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1910            s.select_display_ranges([
 1911                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0)
 1912            ]);
 1913        });
 1914
 1915        // Moving to the beginning of the line should put us at the start of the second line of
 1916        // display text, i.e., the `j`.
 1917        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1918        assert_eq!(
 1919            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1920            display_ranges(editor, cx)
 1921        );
 1922
 1923        // Moving to the beginning of the line again should be a no-op.
 1924        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1925        assert_eq!(
 1926            vec![DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),],
 1927            display_ranges(editor, cx)
 1928        );
 1929
 1930        // Moving to the end of the line should put us right after the `s` that was soft-wrapped to the
 1931        // next display line.
 1932        editor.move_to_end_of_line(&move_to_end, window, cx);
 1933        assert_eq!(
 1934            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1935            display_ranges(editor, cx)
 1936        );
 1937
 1938        // Moving to the end of the line again should be a no-op.
 1939        editor.move_to_end_of_line(&move_to_end, window, cx);
 1940        assert_eq!(
 1941            vec![DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),],
 1942            display_ranges(editor, cx)
 1943        );
 1944    });
 1945}
 1946
 1947#[gpui::test]
 1948fn test_beginning_of_line_stop_at_indent(cx: &mut TestAppContext) {
 1949    init_test(cx, |_| {});
 1950
 1951    let move_to_beg = MoveToBeginningOfLine {
 1952        stop_at_soft_wraps: true,
 1953        stop_at_indent: true,
 1954    };
 1955
 1956    let select_to_beg = SelectToBeginningOfLine {
 1957        stop_at_soft_wraps: true,
 1958        stop_at_indent: true,
 1959    };
 1960
 1961    let delete_to_beg = DeleteToBeginningOfLine {
 1962        stop_at_indent: true,
 1963    };
 1964
 1965    let move_to_end = MoveToEndOfLine {
 1966        stop_at_soft_wraps: false,
 1967    };
 1968
 1969    let editor = cx.add_window(|window, cx| {
 1970        let buffer = MultiBuffer::build_simple("abc\n  def", cx);
 1971        build_editor(buffer, window, cx)
 1972    });
 1973
 1974    _ = editor.update(cx, |editor, window, cx| {
 1975        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 1976            s.select_display_ranges([
 1977                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 1978                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 4),
 1979            ]);
 1980        });
 1981
 1982        // Moving to the beginning of the line should put the first cursor at the beginning of the line,
 1983        // and the second cursor at the first non-whitespace character in the line.
 1984        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1985        assert_eq!(
 1986            display_ranges(editor, cx),
 1987            &[
 1988                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 1989                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 1990            ]
 1991        );
 1992
 1993        // Moving to the beginning of the line again should be a no-op for the first cursor,
 1994        // and should move the second cursor to the beginning of the line.
 1995        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 1996        assert_eq!(
 1997            display_ranges(editor, cx),
 1998            &[
 1999                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2000                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 2001            ]
 2002        );
 2003
 2004        // Moving to the beginning of the line again should still be a no-op for the first cursor,
 2005        // and should move the second cursor back to the first non-whitespace character in the line.
 2006        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2007        assert_eq!(
 2008            display_ranges(editor, cx),
 2009            &[
 2010                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 2011                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 2012            ]
 2013        );
 2014
 2015        // Selecting to the beginning of the line should select to the beginning of the line for the first cursor,
 2016        // and to the first non-whitespace character in the line for the second cursor.
 2017        editor.move_to_end_of_line(&move_to_end, window, cx);
 2018        editor.move_left(&MoveLeft, window, cx);
 2019        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2020        assert_eq!(
 2021            display_ranges(editor, cx),
 2022            &[
 2023                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2024                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 2),
 2025            ]
 2026        );
 2027
 2028        // Selecting to the beginning of the line again should be a no-op for the first cursor,
 2029        // and should select to the beginning of the line for the second cursor.
 2030        editor.select_to_beginning_of_line(&select_to_beg, window, cx);
 2031        assert_eq!(
 2032            display_ranges(editor, cx),
 2033            &[
 2034                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 0),
 2035                DisplayPoint::new(DisplayRow(1), 4)..DisplayPoint::new(DisplayRow(1), 0),
 2036            ]
 2037        );
 2038
 2039        // Deleting to the beginning of the line should delete to the beginning of the line for the first cursor,
 2040        // and should delete to the first non-whitespace character in the line for the second cursor.
 2041        editor.move_to_end_of_line(&move_to_end, window, cx);
 2042        editor.move_left(&MoveLeft, window, cx);
 2043        editor.delete_to_beginning_of_line(&delete_to_beg, window, cx);
 2044        assert_eq!(editor.text(cx), "c\n  f");
 2045    });
 2046}
 2047
 2048#[gpui::test]
 2049fn test_beginning_of_line_with_cursor_between_line_start_and_indent(cx: &mut TestAppContext) {
 2050    init_test(cx, |_| {});
 2051
 2052    let move_to_beg = MoveToBeginningOfLine {
 2053        stop_at_soft_wraps: true,
 2054        stop_at_indent: true,
 2055    };
 2056
 2057    let editor = cx.add_window(|window, cx| {
 2058        let buffer = MultiBuffer::build_simple("    hello\nworld", cx);
 2059        build_editor(buffer, window, cx)
 2060    });
 2061
 2062    _ = editor.update(cx, |editor, window, cx| {
 2063        // test cursor between line_start and indent_start
 2064        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2065            s.select_display_ranges([
 2066                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 3)
 2067            ]);
 2068        });
 2069
 2070        // cursor should move to line_start
 2071        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2072        assert_eq!(
 2073            display_ranges(editor, cx),
 2074            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2075        );
 2076
 2077        // cursor should move to indent_start
 2078        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2079        assert_eq!(
 2080            display_ranges(editor, cx),
 2081            &[DisplayPoint::new(DisplayRow(0), 4)..DisplayPoint::new(DisplayRow(0), 4)]
 2082        );
 2083
 2084        // cursor should move to back to line_start
 2085        editor.move_to_beginning_of_line(&move_to_beg, window, cx);
 2086        assert_eq!(
 2087            display_ranges(editor, cx),
 2088            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 2089        );
 2090    });
 2091}
 2092
 2093#[gpui::test]
 2094fn test_prev_next_word_boundary(cx: &mut TestAppContext) {
 2095    init_test(cx, |_| {});
 2096
 2097    let editor = cx.add_window(|window, cx| {
 2098        let buffer = MultiBuffer::build_simple("use std::str::{foo, bar}\n\n  {baz.qux()}", cx);
 2099        build_editor(buffer, window, cx)
 2100    });
 2101    _ = editor.update(cx, |editor, window, cx| {
 2102        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2103            s.select_display_ranges([
 2104                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11),
 2105                DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4),
 2106            ])
 2107        });
 2108        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2109        assert_selection_ranges("use std::ˇstr::{foo, bar}\n\n  {ˇbaz.qux()}", editor, cx);
 2110
 2111        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2112        assert_selection_ranges("use stdˇ::str::{foo, bar}\n\nˇ  {baz.qux()}", editor, cx);
 2113
 2114        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2115        assert_selection_ranges("use ˇstd::str::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2116
 2117        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2118        assert_selection_ranges("ˇuse std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2119
 2120        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2121        assert_selection_ranges("ˇuse std::str::{foo, ˇbar}\n\n  {baz.qux()}", editor, cx);
 2122
 2123        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2124        assert_selection_ranges("useˇ std::str::{foo, barˇ}\n\n  {baz.qux()}", editor, cx);
 2125
 2126        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2127        assert_selection_ranges("use stdˇ::str::{foo, bar}ˇ\n\n  {baz.qux()}", editor, cx);
 2128
 2129        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2130        assert_selection_ranges("use std::ˇstr::{foo, bar}\nˇ\n  {baz.qux()}", editor, cx);
 2131
 2132        editor.move_right(&MoveRight, window, cx);
 2133        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2134        assert_selection_ranges(
 2135            "use std::«ˇs»tr::{foo, bar}\n«ˇ\n»  {baz.qux()}",
 2136            editor,
 2137            cx,
 2138        );
 2139
 2140        editor.select_to_previous_word_start(&SelectToPreviousWordStart, window, cx);
 2141        assert_selection_ranges(
 2142            "use std«ˇ::s»tr::{foo, bar«ˇ}\n\n»  {baz.qux()}",
 2143            editor,
 2144            cx,
 2145        );
 2146
 2147        editor.select_to_next_word_end(&SelectToNextWordEnd, window, cx);
 2148        assert_selection_ranges(
 2149            "use std::«ˇs»tr::{foo, bar}«ˇ\n\n»  {baz.qux()}",
 2150            editor,
 2151            cx,
 2152        );
 2153    });
 2154}
 2155
 2156#[gpui::test]
 2157fn test_prev_next_word_bounds_with_soft_wrap(cx: &mut TestAppContext) {
 2158    init_test(cx, |_| {});
 2159
 2160    let editor = cx.add_window(|window, cx| {
 2161        let buffer = MultiBuffer::build_simple("use one::{\n    two::three::four::five\n};", cx);
 2162        build_editor(buffer, window, cx)
 2163    });
 2164
 2165    _ = editor.update(cx, |editor, window, cx| {
 2166        editor.set_wrap_width(Some(140.0.into()), cx);
 2167        assert_eq!(
 2168            editor.display_text(cx),
 2169            "use one::{\n    two::three::\n    four::five\n};"
 2170        );
 2171
 2172        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 2173            s.select_display_ranges([
 2174                DisplayPoint::new(DisplayRow(1), 7)..DisplayPoint::new(DisplayRow(1), 7)
 2175            ]);
 2176        });
 2177
 2178        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2179        assert_eq!(
 2180            display_ranges(editor, cx),
 2181            &[DisplayPoint::new(DisplayRow(1), 9)..DisplayPoint::new(DisplayRow(1), 9)]
 2182        );
 2183
 2184        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2185        assert_eq!(
 2186            display_ranges(editor, cx),
 2187            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2188        );
 2189
 2190        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2191        assert_eq!(
 2192            display_ranges(editor, cx),
 2193            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2194        );
 2195
 2196        editor.move_to_next_word_end(&MoveToNextWordEnd, window, cx);
 2197        assert_eq!(
 2198            display_ranges(editor, cx),
 2199            &[DisplayPoint::new(DisplayRow(2), 8)..DisplayPoint::new(DisplayRow(2), 8)]
 2200        );
 2201
 2202        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2203        assert_eq!(
 2204            display_ranges(editor, cx),
 2205            &[DisplayPoint::new(DisplayRow(2), 4)..DisplayPoint::new(DisplayRow(2), 4)]
 2206        );
 2207
 2208        editor.move_to_previous_word_start(&MoveToPreviousWordStart, window, cx);
 2209        assert_eq!(
 2210            display_ranges(editor, cx),
 2211            &[DisplayPoint::new(DisplayRow(1), 14)..DisplayPoint::new(DisplayRow(1), 14)]
 2212        );
 2213    });
 2214}
 2215
 2216#[gpui::test]
 2217async fn test_move_start_of_paragraph_end_of_paragraph(cx: &mut TestAppContext) {
 2218    init_test(cx, |_| {});
 2219    let mut cx = EditorTestContext::new(cx).await;
 2220
 2221    let line_height = cx.editor(|editor, window, _| {
 2222        editor
 2223            .style()
 2224            .unwrap()
 2225            .text
 2226            .line_height_in_pixels(window.rem_size())
 2227    });
 2228    cx.simulate_window_resize(cx.window, size(px(100.), 4. * line_height));
 2229
 2230    cx.set_state(
 2231        &r#"ˇone
 2232        two
 2233
 2234        three
 2235        fourˇ
 2236        five
 2237
 2238        six"#
 2239            .unindent(),
 2240    );
 2241
 2242    cx.update_editor(|editor, window, cx| {
 2243        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2244    });
 2245    cx.assert_editor_state(
 2246        &r#"one
 2247        two
 2248        ˇ
 2249        three
 2250        four
 2251        five
 2252        ˇ
 2253        six"#
 2254            .unindent(),
 2255    );
 2256
 2257    cx.update_editor(|editor, window, cx| {
 2258        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2259    });
 2260    cx.assert_editor_state(
 2261        &r#"one
 2262        two
 2263
 2264        three
 2265        four
 2266        five
 2267        ˇ
 2268        sixˇ"#
 2269            .unindent(),
 2270    );
 2271
 2272    cx.update_editor(|editor, window, cx| {
 2273        editor.move_to_end_of_paragraph(&MoveToEndOfParagraph, window, cx)
 2274    });
 2275    cx.assert_editor_state(
 2276        &r#"one
 2277        two
 2278
 2279        three
 2280        four
 2281        five
 2282
 2283        sixˇ"#
 2284            .unindent(),
 2285    );
 2286
 2287    cx.update_editor(|editor, window, cx| {
 2288        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2289    });
 2290    cx.assert_editor_state(
 2291        &r#"one
 2292        two
 2293
 2294        three
 2295        four
 2296        five
 2297        ˇ
 2298        six"#
 2299            .unindent(),
 2300    );
 2301
 2302    cx.update_editor(|editor, window, cx| {
 2303        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2304    });
 2305    cx.assert_editor_state(
 2306        &r#"one
 2307        two
 2308        ˇ
 2309        three
 2310        four
 2311        five
 2312
 2313        six"#
 2314            .unindent(),
 2315    );
 2316
 2317    cx.update_editor(|editor, window, cx| {
 2318        editor.move_to_start_of_paragraph(&MoveToStartOfParagraph, window, cx)
 2319    });
 2320    cx.assert_editor_state(
 2321        &r#"ˇone
 2322        two
 2323
 2324        three
 2325        four
 2326        five
 2327
 2328        six"#
 2329            .unindent(),
 2330    );
 2331}
 2332
 2333#[gpui::test]
 2334async fn test_scroll_page_up_page_down(cx: &mut TestAppContext) {
 2335    init_test(cx, |_| {});
 2336    let mut cx = EditorTestContext::new(cx).await;
 2337    let line_height = cx.editor(|editor, window, _| {
 2338        editor
 2339            .style()
 2340            .unwrap()
 2341            .text
 2342            .line_height_in_pixels(window.rem_size())
 2343    });
 2344    let window = cx.window;
 2345    cx.simulate_window_resize(window, size(px(1000.), 4. * line_height + px(0.5)));
 2346
 2347    cx.set_state(
 2348        r#"ˇone
 2349        two
 2350        three
 2351        four
 2352        five
 2353        six
 2354        seven
 2355        eight
 2356        nine
 2357        ten
 2358        "#,
 2359    );
 2360
 2361    cx.update_editor(|editor, window, cx| {
 2362        assert_eq!(
 2363            editor.snapshot(window, cx).scroll_position(),
 2364            gpui::Point::new(0., 0.)
 2365        );
 2366        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2367        assert_eq!(
 2368            editor.snapshot(window, cx).scroll_position(),
 2369            gpui::Point::new(0., 3.)
 2370        );
 2371        editor.scroll_screen(&ScrollAmount::Page(1.), window, cx);
 2372        assert_eq!(
 2373            editor.snapshot(window, cx).scroll_position(),
 2374            gpui::Point::new(0., 6.)
 2375        );
 2376        editor.scroll_screen(&ScrollAmount::Page(-1.), window, cx);
 2377        assert_eq!(
 2378            editor.snapshot(window, cx).scroll_position(),
 2379            gpui::Point::new(0., 3.)
 2380        );
 2381
 2382        editor.scroll_screen(&ScrollAmount::Page(-0.5), window, cx);
 2383        assert_eq!(
 2384            editor.snapshot(window, cx).scroll_position(),
 2385            gpui::Point::new(0., 1.)
 2386        );
 2387        editor.scroll_screen(&ScrollAmount::Page(0.5), window, cx);
 2388        assert_eq!(
 2389            editor.snapshot(window, cx).scroll_position(),
 2390            gpui::Point::new(0., 3.)
 2391        );
 2392    });
 2393}
 2394
 2395#[gpui::test]
 2396async fn test_autoscroll(cx: &mut TestAppContext) {
 2397    init_test(cx, |_| {});
 2398    let mut cx = EditorTestContext::new(cx).await;
 2399
 2400    let line_height = cx.update_editor(|editor, window, cx| {
 2401        editor.set_vertical_scroll_margin(2, cx);
 2402        editor
 2403            .style()
 2404            .unwrap()
 2405            .text
 2406            .line_height_in_pixels(window.rem_size())
 2407    });
 2408    let window = cx.window;
 2409    cx.simulate_window_resize(window, size(px(1000.), 6. * line_height));
 2410
 2411    cx.set_state(
 2412        r#"ˇone
 2413            two
 2414            three
 2415            four
 2416            five
 2417            six
 2418            seven
 2419            eight
 2420            nine
 2421            ten
 2422        "#,
 2423    );
 2424    cx.update_editor(|editor, window, cx| {
 2425        assert_eq!(
 2426            editor.snapshot(window, cx).scroll_position(),
 2427            gpui::Point::new(0., 0.0)
 2428        );
 2429    });
 2430
 2431    // Add a cursor below the visible area. Since both cursors cannot fit
 2432    // on screen, the editor autoscrolls to reveal the newest cursor, and
 2433    // allows the vertical scroll margin below that cursor.
 2434    cx.update_editor(|editor, window, cx| {
 2435        editor.change_selections(Default::default(), window, cx, |selections| {
 2436            selections.select_ranges([
 2437                Point::new(0, 0)..Point::new(0, 0),
 2438                Point::new(6, 0)..Point::new(6, 0),
 2439            ]);
 2440        })
 2441    });
 2442    cx.update_editor(|editor, window, cx| {
 2443        assert_eq!(
 2444            editor.snapshot(window, cx).scroll_position(),
 2445            gpui::Point::new(0., 3.0)
 2446        );
 2447    });
 2448
 2449    // Move down. The editor cursor scrolls down to track the newest cursor.
 2450    cx.update_editor(|editor, window, cx| {
 2451        editor.move_down(&Default::default(), window, cx);
 2452    });
 2453    cx.update_editor(|editor, window, cx| {
 2454        assert_eq!(
 2455            editor.snapshot(window, cx).scroll_position(),
 2456            gpui::Point::new(0., 4.0)
 2457        );
 2458    });
 2459
 2460    // Add a cursor above the visible area. Since both cursors fit on screen,
 2461    // the editor scrolls to show both.
 2462    cx.update_editor(|editor, window, cx| {
 2463        editor.change_selections(Default::default(), window, cx, |selections| {
 2464            selections.select_ranges([
 2465                Point::new(1, 0)..Point::new(1, 0),
 2466                Point::new(6, 0)..Point::new(6, 0),
 2467            ]);
 2468        })
 2469    });
 2470    cx.update_editor(|editor, window, cx| {
 2471        assert_eq!(
 2472            editor.snapshot(window, cx).scroll_position(),
 2473            gpui::Point::new(0., 1.0)
 2474        );
 2475    });
 2476}
 2477
 2478#[gpui::test]
 2479async fn test_move_page_up_page_down(cx: &mut TestAppContext) {
 2480    init_test(cx, |_| {});
 2481    let mut cx = EditorTestContext::new(cx).await;
 2482
 2483    let line_height = cx.editor(|editor, window, _cx| {
 2484        editor
 2485            .style()
 2486            .unwrap()
 2487            .text
 2488            .line_height_in_pixels(window.rem_size())
 2489    });
 2490    let window = cx.window;
 2491    cx.simulate_window_resize(window, size(px(100.), 4. * line_height));
 2492    cx.set_state(
 2493        &r#"
 2494        ˇone
 2495        two
 2496        threeˇ
 2497        four
 2498        five
 2499        six
 2500        seven
 2501        eight
 2502        nine
 2503        ten
 2504        "#
 2505        .unindent(),
 2506    );
 2507
 2508    cx.update_editor(|editor, window, cx| {
 2509        editor.move_page_down(&MovePageDown::default(), window, cx)
 2510    });
 2511    cx.assert_editor_state(
 2512        &r#"
 2513        one
 2514        two
 2515        three
 2516        ˇfour
 2517        five
 2518        sixˇ
 2519        seven
 2520        eight
 2521        nine
 2522        ten
 2523        "#
 2524        .unindent(),
 2525    );
 2526
 2527    cx.update_editor(|editor, window, cx| {
 2528        editor.move_page_down(&MovePageDown::default(), window, cx)
 2529    });
 2530    cx.assert_editor_state(
 2531        &r#"
 2532        one
 2533        two
 2534        three
 2535        four
 2536        five
 2537        six
 2538        ˇseven
 2539        eight
 2540        nineˇ
 2541        ten
 2542        "#
 2543        .unindent(),
 2544    );
 2545
 2546    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2547    cx.assert_editor_state(
 2548        &r#"
 2549        one
 2550        two
 2551        three
 2552        ˇfour
 2553        five
 2554        sixˇ
 2555        seven
 2556        eight
 2557        nine
 2558        ten
 2559        "#
 2560        .unindent(),
 2561    );
 2562
 2563    cx.update_editor(|editor, window, cx| editor.move_page_up(&MovePageUp::default(), window, cx));
 2564    cx.assert_editor_state(
 2565        &r#"
 2566        ˇone
 2567        two
 2568        threeˇ
 2569        four
 2570        five
 2571        six
 2572        seven
 2573        eight
 2574        nine
 2575        ten
 2576        "#
 2577        .unindent(),
 2578    );
 2579
 2580    // Test select collapsing
 2581    cx.update_editor(|editor, window, cx| {
 2582        editor.move_page_down(&MovePageDown::default(), window, cx);
 2583        editor.move_page_down(&MovePageDown::default(), window, cx);
 2584        editor.move_page_down(&MovePageDown::default(), window, cx);
 2585    });
 2586    cx.assert_editor_state(
 2587        &r#"
 2588        one
 2589        two
 2590        three
 2591        four
 2592        five
 2593        six
 2594        seven
 2595        eight
 2596        nine
 2597        ˇten
 2598        ˇ"#
 2599        .unindent(),
 2600    );
 2601}
 2602
 2603#[gpui::test]
 2604async fn test_delete_to_beginning_of_line(cx: &mut TestAppContext) {
 2605    init_test(cx, |_| {});
 2606    let mut cx = EditorTestContext::new(cx).await;
 2607    cx.set_state("one «two threeˇ» four");
 2608    cx.update_editor(|editor, window, cx| {
 2609        editor.delete_to_beginning_of_line(
 2610            &DeleteToBeginningOfLine {
 2611                stop_at_indent: false,
 2612            },
 2613            window,
 2614            cx,
 2615        );
 2616        assert_eq!(editor.text(cx), " four");
 2617    });
 2618}
 2619
 2620#[gpui::test]
 2621async fn test_delete_to_word_boundary(cx: &mut TestAppContext) {
 2622    init_test(cx, |_| {});
 2623
 2624    let mut cx = EditorTestContext::new(cx).await;
 2625
 2626    // For an empty selection, the preceding word fragment is deleted.
 2627    // For non-empty selections, only selected characters are deleted.
 2628    cx.set_state("onˇe two t«hreˇ»e four");
 2629    cx.update_editor(|editor, window, cx| {
 2630        editor.delete_to_previous_word_start(
 2631            &DeleteToPreviousWordStart {
 2632                ignore_newlines: false,
 2633                ignore_brackets: false,
 2634            },
 2635            window,
 2636            cx,
 2637        );
 2638    });
 2639    cx.assert_editor_state("ˇe two tˇe four");
 2640
 2641    cx.set_state("e tˇwo te «fˇ»our");
 2642    cx.update_editor(|editor, window, cx| {
 2643        editor.delete_to_next_word_end(
 2644            &DeleteToNextWordEnd {
 2645                ignore_newlines: false,
 2646                ignore_brackets: false,
 2647            },
 2648            window,
 2649            cx,
 2650        );
 2651    });
 2652    cx.assert_editor_state("e tˇ te ˇour");
 2653}
 2654
 2655#[gpui::test]
 2656async fn test_delete_whitespaces(cx: &mut TestAppContext) {
 2657    init_test(cx, |_| {});
 2658
 2659    let mut cx = EditorTestContext::new(cx).await;
 2660
 2661    cx.set_state("here is some text    ˇwith a space");
 2662    cx.update_editor(|editor, window, cx| {
 2663        editor.delete_to_previous_word_start(
 2664            &DeleteToPreviousWordStart {
 2665                ignore_newlines: false,
 2666                ignore_brackets: true,
 2667            },
 2668            window,
 2669            cx,
 2670        );
 2671    });
 2672    // Continuous whitespace sequences are removed entirely, words behind them are not affected by the deletion action.
 2673    cx.assert_editor_state("here is some textˇwith a space");
 2674
 2675    cx.set_state("here is some text    ˇwith a space");
 2676    cx.update_editor(|editor, window, cx| {
 2677        editor.delete_to_previous_word_start(
 2678            &DeleteToPreviousWordStart {
 2679                ignore_newlines: false,
 2680                ignore_brackets: false,
 2681            },
 2682            window,
 2683            cx,
 2684        );
 2685    });
 2686    cx.assert_editor_state("here is some textˇwith a space");
 2687
 2688    cx.set_state("here is some textˇ    with a space");
 2689    cx.update_editor(|editor, window, cx| {
 2690        editor.delete_to_next_word_end(
 2691            &DeleteToNextWordEnd {
 2692                ignore_newlines: false,
 2693                ignore_brackets: true,
 2694            },
 2695            window,
 2696            cx,
 2697        );
 2698    });
 2699    // Same happens in the other direction.
 2700    cx.assert_editor_state("here is some textˇwith a space");
 2701
 2702    cx.set_state("here is some textˇ    with a space");
 2703    cx.update_editor(|editor, window, cx| {
 2704        editor.delete_to_next_word_end(
 2705            &DeleteToNextWordEnd {
 2706                ignore_newlines: false,
 2707                ignore_brackets: false,
 2708            },
 2709            window,
 2710            cx,
 2711        );
 2712    });
 2713    cx.assert_editor_state("here is some textˇwith a space");
 2714
 2715    cx.set_state("here is some textˇ    with a space");
 2716    cx.update_editor(|editor, window, cx| {
 2717        editor.delete_to_next_word_end(
 2718            &DeleteToNextWordEnd {
 2719                ignore_newlines: true,
 2720                ignore_brackets: false,
 2721            },
 2722            window,
 2723            cx,
 2724        );
 2725    });
 2726    cx.assert_editor_state("here is some textˇwith a space");
 2727    cx.update_editor(|editor, window, cx| {
 2728        editor.delete_to_previous_word_start(
 2729            &DeleteToPreviousWordStart {
 2730                ignore_newlines: true,
 2731                ignore_brackets: false,
 2732            },
 2733            window,
 2734            cx,
 2735        );
 2736    });
 2737    cx.assert_editor_state("here is some ˇwith a space");
 2738    cx.update_editor(|editor, window, cx| {
 2739        editor.delete_to_previous_word_start(
 2740            &DeleteToPreviousWordStart {
 2741                ignore_newlines: true,
 2742                ignore_brackets: false,
 2743            },
 2744            window,
 2745            cx,
 2746        );
 2747    });
 2748    // Single whitespaces are removed with the word behind them.
 2749    cx.assert_editor_state("here is ˇwith a space");
 2750    cx.update_editor(|editor, window, cx| {
 2751        editor.delete_to_previous_word_start(
 2752            &DeleteToPreviousWordStart {
 2753                ignore_newlines: true,
 2754                ignore_brackets: false,
 2755            },
 2756            window,
 2757            cx,
 2758        );
 2759    });
 2760    cx.assert_editor_state("here ˇwith a space");
 2761    cx.update_editor(|editor, window, cx| {
 2762        editor.delete_to_previous_word_start(
 2763            &DeleteToPreviousWordStart {
 2764                ignore_newlines: true,
 2765                ignore_brackets: false,
 2766            },
 2767            window,
 2768            cx,
 2769        );
 2770    });
 2771    cx.assert_editor_state("ˇwith a space");
 2772    cx.update_editor(|editor, window, cx| {
 2773        editor.delete_to_previous_word_start(
 2774            &DeleteToPreviousWordStart {
 2775                ignore_newlines: true,
 2776                ignore_brackets: false,
 2777            },
 2778            window,
 2779            cx,
 2780        );
 2781    });
 2782    cx.assert_editor_state("ˇwith a space");
 2783    cx.update_editor(|editor, window, cx| {
 2784        editor.delete_to_next_word_end(
 2785            &DeleteToNextWordEnd {
 2786                ignore_newlines: true,
 2787                ignore_brackets: false,
 2788            },
 2789            window,
 2790            cx,
 2791        );
 2792    });
 2793    // Same happens in the other direction.
 2794    cx.assert_editor_state("ˇ a space");
 2795    cx.update_editor(|editor, window, cx| {
 2796        editor.delete_to_next_word_end(
 2797            &DeleteToNextWordEnd {
 2798                ignore_newlines: true,
 2799                ignore_brackets: false,
 2800            },
 2801            window,
 2802            cx,
 2803        );
 2804    });
 2805    cx.assert_editor_state("ˇ space");
 2806    cx.update_editor(|editor, window, cx| {
 2807        editor.delete_to_next_word_end(
 2808            &DeleteToNextWordEnd {
 2809                ignore_newlines: true,
 2810                ignore_brackets: false,
 2811            },
 2812            window,
 2813            cx,
 2814        );
 2815    });
 2816    cx.assert_editor_state("ˇ");
 2817    cx.update_editor(|editor, window, cx| {
 2818        editor.delete_to_next_word_end(
 2819            &DeleteToNextWordEnd {
 2820                ignore_newlines: true,
 2821                ignore_brackets: false,
 2822            },
 2823            window,
 2824            cx,
 2825        );
 2826    });
 2827    cx.assert_editor_state("ˇ");
 2828    cx.update_editor(|editor, window, cx| {
 2829        editor.delete_to_previous_word_start(
 2830            &DeleteToPreviousWordStart {
 2831                ignore_newlines: true,
 2832                ignore_brackets: false,
 2833            },
 2834            window,
 2835            cx,
 2836        );
 2837    });
 2838    cx.assert_editor_state("ˇ");
 2839}
 2840
 2841#[gpui::test]
 2842async fn test_delete_to_bracket(cx: &mut TestAppContext) {
 2843    init_test(cx, |_| {});
 2844
 2845    let language = Arc::new(
 2846        Language::new(
 2847            LanguageConfig {
 2848                brackets: BracketPairConfig {
 2849                    pairs: vec![
 2850                        BracketPair {
 2851                            start: "\"".to_string(),
 2852                            end: "\"".to_string(),
 2853                            close: true,
 2854                            surround: true,
 2855                            newline: false,
 2856                        },
 2857                        BracketPair {
 2858                            start: "(".to_string(),
 2859                            end: ")".to_string(),
 2860                            close: true,
 2861                            surround: true,
 2862                            newline: true,
 2863                        },
 2864                    ],
 2865                    ..BracketPairConfig::default()
 2866                },
 2867                ..LanguageConfig::default()
 2868            },
 2869            Some(tree_sitter_rust::LANGUAGE.into()),
 2870        )
 2871        .with_brackets_query(
 2872            r#"
 2873                ("(" @open ")" @close)
 2874                ("\"" @open "\"" @close)
 2875            "#,
 2876        )
 2877        .unwrap(),
 2878    );
 2879
 2880    let mut cx = EditorTestContext::new(cx).await;
 2881    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(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_immediate(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_immediate(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_immediate(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_immediate(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_immediate(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_immediate(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_immediate(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_immediate(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_immediate(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_immediate(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_immediate(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_immediate(toml_language, cx));
 4333    let rust_buffer = cx.new(|cx| {
 4334        Buffer::local("const c: usize = 3;\n", cx).with_language_immediate(rust_language, cx)
 4335    });
 4336    let multibuffer = cx.new(|cx| {
 4337        let mut multibuffer = MultiBuffer::new(ReadWrite);
 4338        multibuffer.push_excerpts(
 4339            toml_buffer.clone(),
 4340            [ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0))],
 4341            cx,
 4342        );
 4343        multibuffer.push_excerpts(
 4344            rust_buffer.clone(),
 4345            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
 4346            cx,
 4347        );
 4348        multibuffer
 4349    });
 4350
 4351    cx.add_window(|window, cx| {
 4352        let mut editor = build_editor(multibuffer, window, cx);
 4353
 4354        assert_eq!(
 4355            editor.text(cx),
 4356            indoc! {"
 4357                a = 1
 4358                b = 2
 4359
 4360                const c: usize = 3;
 4361            "}
 4362        );
 4363
 4364        select_ranges(
 4365            &mut editor,
 4366            indoc! {"
 4367                «aˇ» = 1
 4368                b = 2
 4369
 4370                «const c:ˇ» usize = 3;
 4371            "},
 4372            window,
 4373            cx,
 4374        );
 4375
 4376        editor.tab(&Tab, window, cx);
 4377        assert_text_with_selections(
 4378            &mut editor,
 4379            indoc! {"
 4380                  «aˇ» = 1
 4381                b = 2
 4382
 4383                    «const c:ˇ» usize = 3;
 4384            "},
 4385            cx,
 4386        );
 4387        editor.backtab(&Backtab, window, cx);
 4388        assert_text_with_selections(
 4389            &mut editor,
 4390            indoc! {"
 4391                «aˇ» = 1
 4392                b = 2
 4393
 4394                «const c:ˇ» usize = 3;
 4395            "},
 4396            cx,
 4397        );
 4398
 4399        editor
 4400    });
 4401}
 4402
 4403#[gpui::test]
 4404async fn test_backspace(cx: &mut TestAppContext) {
 4405    init_test(cx, |_| {});
 4406
 4407    let mut cx = EditorTestContext::new(cx).await;
 4408
 4409    // Basic backspace
 4410    cx.set_state(indoc! {"
 4411        onˇe two three
 4412        fou«rˇ» five six
 4413        seven «ˇeight nine
 4414        »ten
 4415    "});
 4416    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4417    cx.assert_editor_state(indoc! {"
 4418        oˇe two three
 4419        fouˇ five six
 4420        seven ˇten
 4421    "});
 4422
 4423    // Test backspace inside and around indents
 4424    cx.set_state(indoc! {"
 4425        zero
 4426            ˇone
 4427                ˇtwo
 4428            ˇ ˇ ˇ  three
 4429        ˇ  ˇ  four
 4430    "});
 4431    cx.update_editor(|e, window, cx| e.backspace(&Backspace, window, cx));
 4432    cx.assert_editor_state(indoc! {"
 4433        zero
 4434        ˇone
 4435            ˇtwo
 4436        ˇ  threeˇ  four
 4437    "});
 4438}
 4439
 4440#[gpui::test]
 4441async fn test_delete(cx: &mut TestAppContext) {
 4442    init_test(cx, |_| {});
 4443
 4444    let mut cx = EditorTestContext::new(cx).await;
 4445    cx.set_state(indoc! {"
 4446        onˇe two three
 4447        fou«rˇ» five six
 4448        seven «ˇeight nine
 4449        »ten
 4450    "});
 4451    cx.update_editor(|e, window, cx| e.delete(&Delete, window, cx));
 4452    cx.assert_editor_state(indoc! {"
 4453        onˇ two three
 4454        fouˇ five six
 4455        seven ˇten
 4456    "});
 4457}
 4458
 4459#[gpui::test]
 4460fn test_delete_line(cx: &mut TestAppContext) {
 4461    init_test(cx, |_| {});
 4462
 4463    let editor = cx.add_window(|window, cx| {
 4464        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4465        build_editor(buffer, window, cx)
 4466    });
 4467    _ = editor.update(cx, |editor, window, cx| {
 4468        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4469            s.select_display_ranges([
 4470                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4471                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 4472                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 4473            ])
 4474        });
 4475        editor.delete_line(&DeleteLine, window, cx);
 4476        assert_eq!(editor.display_text(cx), "ghi");
 4477        assert_eq!(
 4478            display_ranges(editor, cx),
 4479            vec![
 4480                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
 4481                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 4482            ]
 4483        );
 4484    });
 4485
 4486    let editor = cx.add_window(|window, cx| {
 4487        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 4488        build_editor(buffer, window, cx)
 4489    });
 4490    _ = editor.update(cx, |editor, window, cx| {
 4491        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4492            s.select_display_ranges([
 4493                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(0), 1)
 4494            ])
 4495        });
 4496        editor.delete_line(&DeleteLine, window, cx);
 4497        assert_eq!(editor.display_text(cx), "ghi\n");
 4498        assert_eq!(
 4499            display_ranges(editor, cx),
 4500            vec![DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1)]
 4501        );
 4502    });
 4503
 4504    let editor = cx.add_window(|window, cx| {
 4505        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n\njkl\nmno", cx);
 4506        build_editor(buffer, window, cx)
 4507    });
 4508    _ = editor.update(cx, |editor, window, cx| {
 4509        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4510            s.select_display_ranges([
 4511                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(2), 1)
 4512            ])
 4513        });
 4514        editor.delete_line(&DeleteLine, window, cx);
 4515        assert_eq!(editor.display_text(cx), "\njkl\nmno");
 4516        assert_eq!(
 4517            display_ranges(editor, cx),
 4518            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 4519        );
 4520    });
 4521}
 4522
 4523#[gpui::test]
 4524fn test_join_lines_with_single_selection(cx: &mut TestAppContext) {
 4525    init_test(cx, |_| {});
 4526
 4527    cx.add_window(|window, cx| {
 4528        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4529        let mut editor = build_editor(buffer.clone(), window, cx);
 4530        let buffer = buffer.read(cx).as_singleton().unwrap();
 4531
 4532        assert_eq!(
 4533            editor
 4534                .selections
 4535                .ranges::<Point>(&editor.display_snapshot(cx)),
 4536            &[Point::new(0, 0)..Point::new(0, 0)]
 4537        );
 4538
 4539        // When on single line, replace newline at end by space
 4540        editor.join_lines(&JoinLines, window, cx);
 4541        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4542        assert_eq!(
 4543            editor
 4544                .selections
 4545                .ranges::<Point>(&editor.display_snapshot(cx)),
 4546            &[Point::new(0, 3)..Point::new(0, 3)]
 4547        );
 4548
 4549        // When multiple lines are selected, remove newlines that are spanned by the selection
 4550        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4551            s.select_ranges([Point::new(0, 5)..Point::new(2, 2)])
 4552        });
 4553        editor.join_lines(&JoinLines, window, cx);
 4554        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc ddd\n\n");
 4555        assert_eq!(
 4556            editor
 4557                .selections
 4558                .ranges::<Point>(&editor.display_snapshot(cx)),
 4559            &[Point::new(0, 11)..Point::new(0, 11)]
 4560        );
 4561
 4562        // Undo should be transactional
 4563        editor.undo(&Undo, window, cx);
 4564        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n\n");
 4565        assert_eq!(
 4566            editor
 4567                .selections
 4568                .ranges::<Point>(&editor.display_snapshot(cx)),
 4569            &[Point::new(0, 5)..Point::new(2, 2)]
 4570        );
 4571
 4572        // When joining an empty line don't insert a space
 4573        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4574            s.select_ranges([Point::new(2, 1)..Point::new(2, 2)])
 4575        });
 4576        editor.join_lines(&JoinLines, window, cx);
 4577        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd\n");
 4578        assert_eq!(
 4579            editor
 4580                .selections
 4581                .ranges::<Point>(&editor.display_snapshot(cx)),
 4582            [Point::new(2, 3)..Point::new(2, 3)]
 4583        );
 4584
 4585        // We can remove trailing newlines
 4586        editor.join_lines(&JoinLines, window, cx);
 4587        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4588        assert_eq!(
 4589            editor
 4590                .selections
 4591                .ranges::<Point>(&editor.display_snapshot(cx)),
 4592            [Point::new(2, 3)..Point::new(2, 3)]
 4593        );
 4594
 4595        // We don't blow up on the last line
 4596        editor.join_lines(&JoinLines, window, cx);
 4597        assert_eq!(buffer.read(cx).text(), "aaa bbb\nccc\nddd");
 4598        assert_eq!(
 4599            editor
 4600                .selections
 4601                .ranges::<Point>(&editor.display_snapshot(cx)),
 4602            [Point::new(2, 3)..Point::new(2, 3)]
 4603        );
 4604
 4605        // reset to test indentation
 4606        editor.buffer.update(cx, |buffer, cx| {
 4607            buffer.edit(
 4608                [
 4609                    (Point::new(1, 0)..Point::new(1, 2), "  "),
 4610                    (Point::new(2, 0)..Point::new(2, 3), "  \n\td"),
 4611                ],
 4612                None,
 4613                cx,
 4614            )
 4615        });
 4616
 4617        // We remove any leading spaces
 4618        assert_eq!(buffer.read(cx).text(), "aaa bbb\n  c\n  \n\td");
 4619        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4620            s.select_ranges([Point::new(0, 1)..Point::new(0, 1)])
 4621        });
 4622        editor.join_lines(&JoinLines, window, cx);
 4623        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n  \n\td");
 4624
 4625        // We don't insert a space for a line containing only spaces
 4626        editor.join_lines(&JoinLines, window, cx);
 4627        assert_eq!(buffer.read(cx).text(), "aaa bbb c\n\td");
 4628
 4629        // We ignore any leading tabs
 4630        editor.join_lines(&JoinLines, window, cx);
 4631        assert_eq!(buffer.read(cx).text(), "aaa bbb c d");
 4632
 4633        editor
 4634    });
 4635}
 4636
 4637#[gpui::test]
 4638fn test_join_lines_with_multi_selection(cx: &mut TestAppContext) {
 4639    init_test(cx, |_| {});
 4640
 4641    cx.add_window(|window, cx| {
 4642        let buffer = MultiBuffer::build_simple("aaa\nbbb\nccc\nddd\n\n", cx);
 4643        let mut editor = build_editor(buffer.clone(), window, cx);
 4644        let buffer = buffer.read(cx).as_singleton().unwrap();
 4645
 4646        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 4647            s.select_ranges([
 4648                Point::new(0, 2)..Point::new(1, 1),
 4649                Point::new(1, 2)..Point::new(1, 2),
 4650                Point::new(3, 1)..Point::new(3, 2),
 4651            ])
 4652        });
 4653
 4654        editor.join_lines(&JoinLines, window, cx);
 4655        assert_eq!(buffer.read(cx).text(), "aaa bbb ccc\nddd\n");
 4656
 4657        assert_eq!(
 4658            editor
 4659                .selections
 4660                .ranges::<Point>(&editor.display_snapshot(cx)),
 4661            [
 4662                Point::new(0, 7)..Point::new(0, 7),
 4663                Point::new(1, 3)..Point::new(1, 3)
 4664            ]
 4665        );
 4666        editor
 4667    });
 4668}
 4669
 4670#[gpui::test]
 4671async fn test_join_lines_with_git_diff_base(executor: BackgroundExecutor, cx: &mut TestAppContext) {
 4672    init_test(cx, |_| {});
 4673
 4674    let mut cx = EditorTestContext::new(cx).await;
 4675
 4676    let diff_base = r#"
 4677        Line 0
 4678        Line 1
 4679        Line 2
 4680        Line 3
 4681        "#
 4682    .unindent();
 4683
 4684    cx.set_state(
 4685        &r#"
 4686        ˇLine 0
 4687        Line 1
 4688        Line 2
 4689        Line 3
 4690        "#
 4691        .unindent(),
 4692    );
 4693
 4694    cx.set_head_text(&diff_base);
 4695    executor.run_until_parked();
 4696
 4697    // Join lines
 4698    cx.update_editor(|editor, window, cx| {
 4699        editor.join_lines(&JoinLines, window, cx);
 4700    });
 4701    executor.run_until_parked();
 4702
 4703    cx.assert_editor_state(
 4704        &r#"
 4705        Line 0ˇ Line 1
 4706        Line 2
 4707        Line 3
 4708        "#
 4709        .unindent(),
 4710    );
 4711    // Join again
 4712    cx.update_editor(|editor, window, cx| {
 4713        editor.join_lines(&JoinLines, window, cx);
 4714    });
 4715    executor.run_until_parked();
 4716
 4717    cx.assert_editor_state(
 4718        &r#"
 4719        Line 0 Line 1ˇ Line 2
 4720        Line 3
 4721        "#
 4722        .unindent(),
 4723    );
 4724}
 4725
 4726#[gpui::test]
 4727async fn test_custom_newlines_cause_no_false_positive_diffs(
 4728    executor: BackgroundExecutor,
 4729    cx: &mut TestAppContext,
 4730) {
 4731    init_test(cx, |_| {});
 4732    let mut cx = EditorTestContext::new(cx).await;
 4733    cx.set_state("Line 0\r\nLine 1\rˇ\nLine 2\r\nLine 3");
 4734    cx.set_head_text("Line 0\r\nLine 1\r\nLine 2\r\nLine 3");
 4735    executor.run_until_parked();
 4736
 4737    cx.update_editor(|editor, window, cx| {
 4738        let snapshot = editor.snapshot(window, cx);
 4739        assert_eq!(
 4740            snapshot
 4741                .buffer_snapshot()
 4742                .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
 4743                .collect::<Vec<_>>(),
 4744            Vec::new(),
 4745            "Should not have any diffs for files with custom newlines"
 4746        );
 4747    });
 4748}
 4749
 4750#[gpui::test]
 4751async fn test_manipulate_immutable_lines_with_single_selection(cx: &mut TestAppContext) {
 4752    init_test(cx, |_| {});
 4753
 4754    let mut cx = EditorTestContext::new(cx).await;
 4755
 4756    // Test sort_lines_case_insensitive()
 4757    cx.set_state(indoc! {"
 4758        «z
 4759        y
 4760        x
 4761        Z
 4762        Y
 4763        Xˇ»
 4764    "});
 4765    cx.update_editor(|e, window, cx| {
 4766        e.sort_lines_case_insensitive(&SortLinesCaseInsensitive, window, cx)
 4767    });
 4768    cx.assert_editor_state(indoc! {"
 4769        «x
 4770        X
 4771        y
 4772        Y
 4773        z
 4774        Zˇ»
 4775    "});
 4776
 4777    // Test sort_lines_by_length()
 4778    //
 4779    // Demonstrates:
 4780    // - ∞ is 3 bytes UTF-8, but sorted by its char count (1)
 4781    // - sort is stable
 4782    cx.set_state(indoc! {"
 4783        «123
 4784        æ
 4785        12
 4786 4787        1
 4788        æˇ»
 4789    "});
 4790    cx.update_editor(|e, window, cx| e.sort_lines_by_length(&SortLinesByLength, window, cx));
 4791    cx.assert_editor_state(indoc! {"
 4792        «æ
 4793 4794        1
 4795        æ
 4796        12
 4797        123ˇ»
 4798    "});
 4799
 4800    // Test reverse_lines()
 4801    cx.set_state(indoc! {"
 4802        «5
 4803        4
 4804        3
 4805        2
 4806        1ˇ»
 4807    "});
 4808    cx.update_editor(|e, window, cx| e.reverse_lines(&ReverseLines, window, cx));
 4809    cx.assert_editor_state(indoc! {"
 4810        «1
 4811        2
 4812        3
 4813        4
 4814        5ˇ»
 4815    "});
 4816
 4817    // Skip testing shuffle_line()
 4818
 4819    // From here on out, test more complex cases of manipulate_immutable_lines() with a single driver method: sort_lines_case_sensitive()
 4820    // Since all methods calling manipulate_immutable_lines() are doing the exact same general thing (reordering lines)
 4821
 4822    // Don't manipulate when cursor is on single line, but expand the selection
 4823    cx.set_state(indoc! {"
 4824        ddˇdd
 4825        ccc
 4826        bb
 4827        a
 4828    "});
 4829    cx.update_editor(|e, window, cx| {
 4830        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4831    });
 4832    cx.assert_editor_state(indoc! {"
 4833        «ddddˇ»
 4834        ccc
 4835        bb
 4836        a
 4837    "});
 4838
 4839    // Basic manipulate case
 4840    // Start selection moves to column 0
 4841    // End of selection shrinks to fit shorter line
 4842    cx.set_state(indoc! {"
 4843        dd«d
 4844        ccc
 4845        bb
 4846        aaaaaˇ»
 4847    "});
 4848    cx.update_editor(|e, window, cx| {
 4849        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4850    });
 4851    cx.assert_editor_state(indoc! {"
 4852        «aaaaa
 4853        bb
 4854        ccc
 4855        dddˇ»
 4856    "});
 4857
 4858    // Manipulate case with newlines
 4859    cx.set_state(indoc! {"
 4860        dd«d
 4861        ccc
 4862
 4863        bb
 4864        aaaaa
 4865
 4866        ˇ»
 4867    "});
 4868    cx.update_editor(|e, window, cx| {
 4869        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 4870    });
 4871    cx.assert_editor_state(indoc! {"
 4872        «
 4873
 4874        aaaaa
 4875        bb
 4876        ccc
 4877        dddˇ»
 4878
 4879    "});
 4880
 4881    // Adding new line
 4882    cx.set_state(indoc! {"
 4883        aa«a
 4884        bbˇ»b
 4885    "});
 4886    cx.update_editor(|e, window, cx| {
 4887        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added_line"))
 4888    });
 4889    cx.assert_editor_state(indoc! {"
 4890        «aaa
 4891        bbb
 4892        added_lineˇ»
 4893    "});
 4894
 4895    // Removing line
 4896    cx.set_state(indoc! {"
 4897        aa«a
 4898        bbbˇ»
 4899    "});
 4900    cx.update_editor(|e, window, cx| {
 4901        e.manipulate_immutable_lines(window, cx, |lines| {
 4902            lines.pop();
 4903        })
 4904    });
 4905    cx.assert_editor_state(indoc! {"
 4906        «aaaˇ»
 4907    "});
 4908
 4909    // Removing all lines
 4910    cx.set_state(indoc! {"
 4911        aa«a
 4912        bbbˇ»
 4913    "});
 4914    cx.update_editor(|e, window, cx| {
 4915        e.manipulate_immutable_lines(window, cx, |lines| {
 4916            lines.drain(..);
 4917        })
 4918    });
 4919    cx.assert_editor_state(indoc! {"
 4920        ˇ
 4921    "});
 4922}
 4923
 4924#[gpui::test]
 4925async fn test_unique_lines_multi_selection(cx: &mut TestAppContext) {
 4926    init_test(cx, |_| {});
 4927
 4928    let mut cx = EditorTestContext::new(cx).await;
 4929
 4930    // Consider continuous selection as single selection
 4931    cx.set_state(indoc! {"
 4932        Aaa«aa
 4933        cˇ»c«c
 4934        bb
 4935        aaaˇ»aa
 4936    "});
 4937    cx.update_editor(|e, window, cx| {
 4938        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4939    });
 4940    cx.assert_editor_state(indoc! {"
 4941        «Aaaaa
 4942        ccc
 4943        bb
 4944        aaaaaˇ»
 4945    "});
 4946
 4947    cx.set_state(indoc! {"
 4948        Aaa«aa
 4949        cˇ»c«c
 4950        bb
 4951        aaaˇ»aa
 4952    "});
 4953    cx.update_editor(|e, window, cx| {
 4954        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 4955    });
 4956    cx.assert_editor_state(indoc! {"
 4957        «Aaaaa
 4958        ccc
 4959        bbˇ»
 4960    "});
 4961
 4962    // Consider non continuous selection as distinct dedup operations
 4963    cx.set_state(indoc! {"
 4964        «aaaaa
 4965        bb
 4966        aaaaa
 4967        aaaaaˇ»
 4968
 4969        aaa«aaˇ»
 4970    "});
 4971    cx.update_editor(|e, window, cx| {
 4972        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4973    });
 4974    cx.assert_editor_state(indoc! {"
 4975        «aaaaa
 4976        bbˇ»
 4977
 4978        «aaaaaˇ»
 4979    "});
 4980}
 4981
 4982#[gpui::test]
 4983async fn test_unique_lines_single_selection(cx: &mut TestAppContext) {
 4984    init_test(cx, |_| {});
 4985
 4986    let mut cx = EditorTestContext::new(cx).await;
 4987
 4988    cx.set_state(indoc! {"
 4989        «Aaa
 4990        aAa
 4991        Aaaˇ»
 4992    "});
 4993    cx.update_editor(|e, window, cx| {
 4994        e.unique_lines_case_sensitive(&UniqueLinesCaseSensitive, window, cx)
 4995    });
 4996    cx.assert_editor_state(indoc! {"
 4997        «Aaa
 4998        aAaˇ»
 4999    "});
 5000
 5001    cx.set_state(indoc! {"
 5002        «Aaa
 5003        aAa
 5004        aaAˇ»
 5005    "});
 5006    cx.update_editor(|e, window, cx| {
 5007        e.unique_lines_case_insensitive(&UniqueLinesCaseInsensitive, window, cx)
 5008    });
 5009    cx.assert_editor_state(indoc! {"
 5010        «Aaaˇ»
 5011    "});
 5012}
 5013
 5014#[gpui::test]
 5015async fn test_wrap_in_tag_single_selection(cx: &mut TestAppContext) {
 5016    init_test(cx, |_| {});
 5017
 5018    let mut cx = EditorTestContext::new(cx).await;
 5019
 5020    let js_language = Arc::new(Language::new(
 5021        LanguageConfig {
 5022            name: "JavaScript".into(),
 5023            wrap_characters: Some(language::WrapCharactersConfig {
 5024                start_prefix: "<".into(),
 5025                start_suffix: ">".into(),
 5026                end_prefix: "</".into(),
 5027                end_suffix: ">".into(),
 5028            }),
 5029            ..LanguageConfig::default()
 5030        },
 5031        None,
 5032    ));
 5033
 5034    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(js_language), cx));
 5035
 5036    cx.set_state(indoc! {"
 5037        «testˇ»
 5038    "});
 5039    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5040    cx.assert_editor_state(indoc! {"
 5041        <«ˇ»>test</«ˇ»>
 5042    "});
 5043
 5044    cx.set_state(indoc! {"
 5045        «test
 5046         testˇ»
 5047    "});
 5048    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5049    cx.assert_editor_state(indoc! {"
 5050        <«ˇ»>test
 5051         test</«ˇ»>
 5052    "});
 5053
 5054    cx.set_state(indoc! {"
 5055        teˇst
 5056    "});
 5057    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5058    cx.assert_editor_state(indoc! {"
 5059        te<«ˇ»></«ˇ»>st
 5060    "});
 5061}
 5062
 5063#[gpui::test]
 5064async fn test_wrap_in_tag_multi_selection(cx: &mut TestAppContext) {
 5065    init_test(cx, |_| {});
 5066
 5067    let mut cx = EditorTestContext::new(cx).await;
 5068
 5069    let js_language = Arc::new(Language::new(
 5070        LanguageConfig {
 5071            name: "JavaScript".into(),
 5072            wrap_characters: Some(language::WrapCharactersConfig {
 5073                start_prefix: "<".into(),
 5074                start_suffix: ">".into(),
 5075                end_prefix: "</".into(),
 5076                end_suffix: ">".into(),
 5077            }),
 5078            ..LanguageConfig::default()
 5079        },
 5080        None,
 5081    ));
 5082
 5083    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(js_language), cx));
 5084
 5085    cx.set_state(indoc! {"
 5086        «testˇ»
 5087        «testˇ» «testˇ»
 5088        «testˇ»
 5089    "});
 5090    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5091    cx.assert_editor_state(indoc! {"
 5092        <«ˇ»>test</«ˇ»>
 5093        <«ˇ»>test</«ˇ»> <«ˇ»>test</«ˇ»>
 5094        <«ˇ»>test</«ˇ»>
 5095    "});
 5096
 5097    cx.set_state(indoc! {"
 5098        «test
 5099         testˇ»
 5100        «test
 5101         testˇ»
 5102    "});
 5103    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5104    cx.assert_editor_state(indoc! {"
 5105        <«ˇ»>test
 5106         test</«ˇ»>
 5107        <«ˇ»>test
 5108         test</«ˇ»>
 5109    "});
 5110}
 5111
 5112#[gpui::test]
 5113async fn test_wrap_in_tag_does_nothing_in_unsupported_languages(cx: &mut TestAppContext) {
 5114    init_test(cx, |_| {});
 5115
 5116    let mut cx = EditorTestContext::new(cx).await;
 5117
 5118    let plaintext_language = Arc::new(Language::new(
 5119        LanguageConfig {
 5120            name: "Plain Text".into(),
 5121            ..LanguageConfig::default()
 5122        },
 5123        None,
 5124    ));
 5125
 5126    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(plaintext_language), cx));
 5127
 5128    cx.set_state(indoc! {"
 5129        «testˇ»
 5130    "});
 5131    cx.update_editor(|e, window, cx| e.wrap_selections_in_tag(&WrapSelectionsInTag, window, cx));
 5132    cx.assert_editor_state(indoc! {"
 5133      «testˇ»
 5134    "});
 5135}
 5136
 5137#[gpui::test]
 5138async fn test_manipulate_immutable_lines_with_multi_selection(cx: &mut TestAppContext) {
 5139    init_test(cx, |_| {});
 5140
 5141    let mut cx = EditorTestContext::new(cx).await;
 5142
 5143    // Manipulate with multiple selections on a single line
 5144    cx.set_state(indoc! {"
 5145        dd«dd
 5146        cˇ»c«c
 5147        bb
 5148        aaaˇ»aa
 5149    "});
 5150    cx.update_editor(|e, window, cx| {
 5151        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5152    });
 5153    cx.assert_editor_state(indoc! {"
 5154        «aaaaa
 5155        bb
 5156        ccc
 5157        ddddˇ»
 5158    "});
 5159
 5160    // Manipulate with multiple disjoin selections
 5161    cx.set_state(indoc! {"
 5162 5163        4
 5164        3
 5165        2
 5166        1ˇ»
 5167
 5168        dd«dd
 5169        ccc
 5170        bb
 5171        aaaˇ»aa
 5172    "});
 5173    cx.update_editor(|e, window, cx| {
 5174        e.sort_lines_case_sensitive(&SortLinesCaseSensitive, window, cx)
 5175    });
 5176    cx.assert_editor_state(indoc! {"
 5177        «1
 5178        2
 5179        3
 5180        4
 5181        5ˇ»
 5182
 5183        «aaaaa
 5184        bb
 5185        ccc
 5186        ddddˇ»
 5187    "});
 5188
 5189    // Adding lines on each selection
 5190    cx.set_state(indoc! {"
 5191 5192        1ˇ»
 5193
 5194        bb«bb
 5195        aaaˇ»aa
 5196    "});
 5197    cx.update_editor(|e, window, cx| {
 5198        e.manipulate_immutable_lines(window, cx, |lines| lines.push("added line"))
 5199    });
 5200    cx.assert_editor_state(indoc! {"
 5201        «2
 5202        1
 5203        added lineˇ»
 5204
 5205        «bbbb
 5206        aaaaa
 5207        added lineˇ»
 5208    "});
 5209
 5210    // Removing lines on each selection
 5211    cx.set_state(indoc! {"
 5212 5213        1ˇ»
 5214
 5215        bb«bb
 5216        aaaˇ»aa
 5217    "});
 5218    cx.update_editor(|e, window, cx| {
 5219        e.manipulate_immutable_lines(window, cx, |lines| {
 5220            lines.pop();
 5221        })
 5222    });
 5223    cx.assert_editor_state(indoc! {"
 5224        «2ˇ»
 5225
 5226        «bbbbˇ»
 5227    "});
 5228}
 5229
 5230#[gpui::test]
 5231async fn test_convert_indentation_to_spaces(cx: &mut TestAppContext) {
 5232    init_test(cx, |settings| {
 5233        settings.defaults.tab_size = NonZeroU32::new(3)
 5234    });
 5235
 5236    let mut cx = EditorTestContext::new(cx).await;
 5237
 5238    // MULTI SELECTION
 5239    // Ln.1 "«" tests empty lines
 5240    // Ln.9 tests just leading whitespace
 5241    cx.set_state(indoc! {"
 5242        «
 5243        abc                 // No indentationˇ»
 5244        «\tabc              // 1 tabˇ»
 5245        \t\tabc «      ˇ»   // 2 tabs
 5246        \t ab«c             // Tab followed by space
 5247         \tabc              // Space followed by tab (3 spaces should be the result)
 5248        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5249           abˇ»ˇc   ˇ    ˇ  // Already space indented«
 5250        \t
 5251        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5252    "});
 5253    cx.update_editor(|e, window, cx| {
 5254        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5255    });
 5256    cx.assert_editor_state(
 5257        indoc! {"
 5258            «
 5259            abc                 // No indentation
 5260               abc              // 1 tab
 5261                  abc          // 2 tabs
 5262                abc             // Tab followed by space
 5263               abc              // Space followed by tab (3 spaces should be the result)
 5264                           abc   // Mixed indentation (tab conversion depends on the column)
 5265               abc         // Already space indented
 5266               ·
 5267               abc\tdef          // Only the leading tab is manipulatedˇ»
 5268        "}
 5269        .replace("·", "")
 5270        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5271    );
 5272
 5273    // Test on just a few lines, the others should remain unchanged
 5274    // Only lines (3, 5, 10, 11) should change
 5275    cx.set_state(
 5276        indoc! {"
 5277            ·
 5278            abc                 // No indentation
 5279            \tabcˇ               // 1 tab
 5280            \t\tabc             // 2 tabs
 5281            \t abcˇ              // Tab followed by space
 5282             \tabc              // Space followed by tab (3 spaces should be the result)
 5283            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5284               abc              // Already space indented
 5285            «\t
 5286            \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5287        "}
 5288        .replace("·", "")
 5289        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5290    );
 5291    cx.update_editor(|e, window, cx| {
 5292        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5293    });
 5294    cx.assert_editor_state(
 5295        indoc! {"
 5296            ·
 5297            abc                 // No indentation
 5298            «   abc               // 1 tabˇ»
 5299            \t\tabc             // 2 tabs
 5300            «    abc              // Tab followed by spaceˇ»
 5301             \tabc              // Space followed by tab (3 spaces should be the result)
 5302            \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5303               abc              // Already space indented
 5304            «   ·
 5305               abc\tdef          // Only the leading tab is manipulatedˇ»
 5306        "}
 5307        .replace("·", "")
 5308        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5309    );
 5310
 5311    // SINGLE SELECTION
 5312    // Ln.1 "«" tests empty lines
 5313    // Ln.9 tests just leading whitespace
 5314    cx.set_state(indoc! {"
 5315        «
 5316        abc                 // No indentation
 5317        \tabc               // 1 tab
 5318        \t\tabc             // 2 tabs
 5319        \t abc              // Tab followed by space
 5320         \tabc              // Space followed by tab (3 spaces should be the result)
 5321        \t \t  \t   \tabc   // Mixed indentation (tab conversion depends on the column)
 5322           abc              // Already space indented
 5323        \t
 5324        \tabc\tdef          // Only the leading tab is manipulatedˇ»
 5325    "});
 5326    cx.update_editor(|e, window, cx| {
 5327        e.convert_indentation_to_spaces(&ConvertIndentationToSpaces, window, cx);
 5328    });
 5329    cx.assert_editor_state(
 5330        indoc! {"
 5331            «
 5332            abc                 // No indentation
 5333               abc               // 1 tab
 5334                  abc             // 2 tabs
 5335                abc              // Tab followed by space
 5336               abc              // Space followed by tab (3 spaces should be the result)
 5337                           abc   // Mixed indentation (tab conversion depends on the column)
 5338               abc              // Already space indented
 5339               ·
 5340               abc\tdef          // Only the leading tab is manipulatedˇ»
 5341        "}
 5342        .replace("·", "")
 5343        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5344    );
 5345}
 5346
 5347#[gpui::test]
 5348async fn test_convert_indentation_to_tabs(cx: &mut TestAppContext) {
 5349    init_test(cx, |settings| {
 5350        settings.defaults.tab_size = NonZeroU32::new(3)
 5351    });
 5352
 5353    let mut cx = EditorTestContext::new(cx).await;
 5354
 5355    // MULTI SELECTION
 5356    // Ln.1 "«" tests empty lines
 5357    // Ln.11 tests just leading whitespace
 5358    cx.set_state(indoc! {"
 5359        «
 5360        abˇ»ˇc                 // No indentation
 5361         abc    ˇ        ˇ    // 1 space (< 3 so dont convert)
 5362          abc  «             // 2 spaces (< 3 so dont convert)
 5363           abc              // 3 spaces (convert)
 5364             abc ˇ»           // 5 spaces (1 tab + 2 spaces)
 5365        «\tˇ»\t«\tˇ»abc           // Already tab indented
 5366        «\t abc              // Tab followed by space
 5367         \tabc              // Space followed by tab (should be consumed due to tab)
 5368        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5369           \tˇ»  «\t
 5370           abcˇ»   \t ˇˇˇ        // Only the leading spaces should be converted
 5371    "});
 5372    cx.update_editor(|e, window, cx| {
 5373        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5374    });
 5375    cx.assert_editor_state(indoc! {"
 5376        «
 5377        abc                 // No indentation
 5378         abc                // 1 space (< 3 so dont convert)
 5379          abc               // 2 spaces (< 3 so dont convert)
 5380        \tabc              // 3 spaces (convert)
 5381        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5382        \t\t\tabc           // Already tab indented
 5383        \t abc              // Tab followed by space
 5384        \tabc              // Space followed by tab (should be consumed due to tab)
 5385        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5386        \t\t\t
 5387        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5388    "});
 5389
 5390    // Test on just a few lines, the other should remain unchanged
 5391    // Only lines (4, 8, 11, 12) should change
 5392    cx.set_state(
 5393        indoc! {"
 5394            ·
 5395            abc                 // No indentation
 5396             abc                // 1 space (< 3 so dont convert)
 5397              abc               // 2 spaces (< 3 so dont convert)
 5398            «   abc              // 3 spaces (convert)ˇ»
 5399                 abc            // 5 spaces (1 tab + 2 spaces)
 5400            \t\t\tabc           // Already tab indented
 5401            \t abc              // Tab followed by space
 5402             \tabc      ˇ        // Space followed by tab (should be consumed due to tab)
 5403               \t\t  \tabc      // Mixed indentation
 5404            \t \t  \t   \tabc   // Mixed indentation
 5405               \t  \tˇ
 5406            «   abc   \t         // Only the leading spaces should be convertedˇ»
 5407        "}
 5408        .replace("·", "")
 5409        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5410    );
 5411    cx.update_editor(|e, window, cx| {
 5412        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5413    });
 5414    cx.assert_editor_state(
 5415        indoc! {"
 5416            ·
 5417            abc                 // No indentation
 5418             abc                // 1 space (< 3 so dont convert)
 5419              abc               // 2 spaces (< 3 so dont convert)
 5420            «\tabc              // 3 spaces (convert)ˇ»
 5421                 abc            // 5 spaces (1 tab + 2 spaces)
 5422            \t\t\tabc           // Already tab indented
 5423            \t abc              // Tab followed by space
 5424            «\tabc              // Space followed by tab (should be consumed due to tab)ˇ»
 5425               \t\t  \tabc      // Mixed indentation
 5426            \t \t  \t   \tabc   // Mixed indentation
 5427            «\t\t\t
 5428            \tabc   \t         // Only the leading spaces should be convertedˇ»
 5429        "}
 5430        .replace("·", "")
 5431        .as_str(), // · used as placeholder to prevent format-on-save from removing whitespace
 5432    );
 5433
 5434    // SINGLE SELECTION
 5435    // Ln.1 "«" tests empty lines
 5436    // Ln.11 tests just leading whitespace
 5437    cx.set_state(indoc! {"
 5438        «
 5439        abc                 // No indentation
 5440         abc                // 1 space (< 3 so dont convert)
 5441          abc               // 2 spaces (< 3 so dont convert)
 5442           abc              // 3 spaces (convert)
 5443             abc            // 5 spaces (1 tab + 2 spaces)
 5444        \t\t\tabc           // Already tab indented
 5445        \t abc              // Tab followed by space
 5446         \tabc              // Space followed by tab (should be consumed due to tab)
 5447        \t \t  \t   \tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5448           \t  \t
 5449           abc   \t         // Only the leading spaces should be convertedˇ»
 5450    "});
 5451    cx.update_editor(|e, window, cx| {
 5452        e.convert_indentation_to_tabs(&ConvertIndentationToTabs, window, cx);
 5453    });
 5454    cx.assert_editor_state(indoc! {"
 5455        «
 5456        abc                 // No indentation
 5457         abc                // 1 space (< 3 so dont convert)
 5458          abc               // 2 spaces (< 3 so dont convert)
 5459        \tabc              // 3 spaces (convert)
 5460        \t  abc            // 5 spaces (1 tab + 2 spaces)
 5461        \t\t\tabc           // Already tab indented
 5462        \t abc              // Tab followed by space
 5463        \tabc              // Space followed by tab (should be consumed due to tab)
 5464        \t\t\t\t\tabc   // Mixed indentation (first 3 spaces are consumed, the others are converted)
 5465        \t\t\t
 5466        \tabc   \t         // Only the leading spaces should be convertedˇ»
 5467    "});
 5468}
 5469
 5470#[gpui::test]
 5471async fn test_toggle_case(cx: &mut TestAppContext) {
 5472    init_test(cx, |_| {});
 5473
 5474    let mut cx = EditorTestContext::new(cx).await;
 5475
 5476    // If all lower case -> upper case
 5477    cx.set_state(indoc! {"
 5478        «hello worldˇ»
 5479    "});
 5480    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5481    cx.assert_editor_state(indoc! {"
 5482        «HELLO WORLDˇ»
 5483    "});
 5484
 5485    // If all upper case -> lower case
 5486    cx.set_state(indoc! {"
 5487        «HELLO WORLDˇ»
 5488    "});
 5489    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5490    cx.assert_editor_state(indoc! {"
 5491        «hello worldˇ»
 5492    "});
 5493
 5494    // If any upper case characters are identified -> lower case
 5495    // This matches JetBrains IDEs
 5496    cx.set_state(indoc! {"
 5497        «hEllo worldˇ»
 5498    "});
 5499    cx.update_editor(|e, window, cx| e.toggle_case(&ToggleCase, window, cx));
 5500    cx.assert_editor_state(indoc! {"
 5501        «hello worldˇ»
 5502    "});
 5503}
 5504
 5505#[gpui::test]
 5506async fn test_convert_to_sentence_case(cx: &mut TestAppContext) {
 5507    init_test(cx, |_| {});
 5508
 5509    let mut cx = EditorTestContext::new(cx).await;
 5510
 5511    cx.set_state(indoc! {"
 5512        «implement-windows-supportˇ»
 5513    "});
 5514    cx.update_editor(|e, window, cx| {
 5515        e.convert_to_sentence_case(&ConvertToSentenceCase, window, cx)
 5516    });
 5517    cx.assert_editor_state(indoc! {"
 5518        «Implement windows supportˇ»
 5519    "});
 5520}
 5521
 5522#[gpui::test]
 5523async fn test_manipulate_text(cx: &mut TestAppContext) {
 5524    init_test(cx, |_| {});
 5525
 5526    let mut cx = EditorTestContext::new(cx).await;
 5527
 5528    // Test convert_to_upper_case()
 5529    cx.set_state(indoc! {"
 5530        «hello worldˇ»
 5531    "});
 5532    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5533    cx.assert_editor_state(indoc! {"
 5534        «HELLO WORLDˇ»
 5535    "});
 5536
 5537    // Test convert_to_lower_case()
 5538    cx.set_state(indoc! {"
 5539        «HELLO WORLDˇ»
 5540    "});
 5541    cx.update_editor(|e, window, cx| e.convert_to_lower_case(&ConvertToLowerCase, window, cx));
 5542    cx.assert_editor_state(indoc! {"
 5543        «hello worldˇ»
 5544    "});
 5545
 5546    // Test multiple line, single selection case
 5547    cx.set_state(indoc! {"
 5548        «The quick brown
 5549        fox jumps over
 5550        the lazy dogˇ»
 5551    "});
 5552    cx.update_editor(|e, window, cx| e.convert_to_title_case(&ConvertToTitleCase, window, cx));
 5553    cx.assert_editor_state(indoc! {"
 5554        «The Quick Brown
 5555        Fox Jumps Over
 5556        The Lazy Dogˇ»
 5557    "});
 5558
 5559    // Test multiple line, single selection case
 5560    cx.set_state(indoc! {"
 5561        «The quick brown
 5562        fox jumps over
 5563        the lazy dogˇ»
 5564    "});
 5565    cx.update_editor(|e, window, cx| {
 5566        e.convert_to_upper_camel_case(&ConvertToUpperCamelCase, window, cx)
 5567    });
 5568    cx.assert_editor_state(indoc! {"
 5569        «TheQuickBrown
 5570        FoxJumpsOver
 5571        TheLazyDogˇ»
 5572    "});
 5573
 5574    // From here on out, test more complex cases of manipulate_text()
 5575
 5576    // Test no selection case - should affect words cursors are in
 5577    // Cursor at beginning, middle, and end of word
 5578    cx.set_state(indoc! {"
 5579        ˇhello big beauˇtiful worldˇ
 5580    "});
 5581    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5582    cx.assert_editor_state(indoc! {"
 5583        «HELLOˇ» big «BEAUTIFULˇ» «WORLDˇ»
 5584    "});
 5585
 5586    // Test multiple selections on a single line and across multiple lines
 5587    cx.set_state(indoc! {"
 5588        «Theˇ» quick «brown
 5589        foxˇ» jumps «overˇ»
 5590        the «lazyˇ» dog
 5591    "});
 5592    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5593    cx.assert_editor_state(indoc! {"
 5594        «THEˇ» quick «BROWN
 5595        FOXˇ» jumps «OVERˇ»
 5596        the «LAZYˇ» dog
 5597    "});
 5598
 5599    // Test case where text length grows
 5600    cx.set_state(indoc! {"
 5601        «tschüߡ»
 5602    "});
 5603    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5604    cx.assert_editor_state(indoc! {"
 5605        «TSCHÜSSˇ»
 5606    "});
 5607
 5608    // Test to make sure we don't crash when text shrinks
 5609    cx.set_state(indoc! {"
 5610        aaa_bbbˇ
 5611    "});
 5612    cx.update_editor(|e, window, cx| {
 5613        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5614    });
 5615    cx.assert_editor_state(indoc! {"
 5616        «aaaBbbˇ»
 5617    "});
 5618
 5619    // Test to make sure we all aware of the fact that each word can grow and shrink
 5620    // Final selections should be aware of this fact
 5621    cx.set_state(indoc! {"
 5622        aaa_bˇbb bbˇb_ccc ˇccc_ddd
 5623    "});
 5624    cx.update_editor(|e, window, cx| {
 5625        e.convert_to_lower_camel_case(&ConvertToLowerCamelCase, window, cx)
 5626    });
 5627    cx.assert_editor_state(indoc! {"
 5628        «aaaBbbˇ» «bbbCccˇ» «cccDddˇ»
 5629    "});
 5630
 5631    cx.set_state(indoc! {"
 5632        «hElLo, WoRld!ˇ»
 5633    "});
 5634    cx.update_editor(|e, window, cx| {
 5635        e.convert_to_opposite_case(&ConvertToOppositeCase, window, cx)
 5636    });
 5637    cx.assert_editor_state(indoc! {"
 5638        «HeLlO, wOrLD!ˇ»
 5639    "});
 5640
 5641    // Test selections with `line_mode() = true`.
 5642    cx.update_editor(|editor, _window, _cx| editor.selections.set_line_mode(true));
 5643    cx.set_state(indoc! {"
 5644        «The quick brown
 5645        fox jumps over
 5646        tˇ»he lazy dog
 5647    "});
 5648    cx.update_editor(|e, window, cx| e.convert_to_upper_case(&ConvertToUpperCase, window, cx));
 5649    cx.assert_editor_state(indoc! {"
 5650        «THE QUICK BROWN
 5651        FOX JUMPS OVER
 5652        THE LAZY DOGˇ»
 5653    "});
 5654}
 5655
 5656#[gpui::test]
 5657fn test_duplicate_line(cx: &mut TestAppContext) {
 5658    init_test(cx, |_| {});
 5659
 5660    let editor = cx.add_window(|window, cx| {
 5661        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5662        build_editor(buffer, window, cx)
 5663    });
 5664    _ = editor.update(cx, |editor, window, cx| {
 5665        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5666            s.select_display_ranges([
 5667                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5668                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5669                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5670                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5671            ])
 5672        });
 5673        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5674        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5675        assert_eq!(
 5676            display_ranges(editor, cx),
 5677            vec![
 5678                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
 5679                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(1), 2),
 5680                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5681                DisplayPoint::new(DisplayRow(6), 0)..DisplayPoint::new(DisplayRow(6), 0),
 5682            ]
 5683        );
 5684    });
 5685
 5686    let editor = cx.add_window(|window, cx| {
 5687        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5688        build_editor(buffer, window, cx)
 5689    });
 5690    _ = editor.update(cx, |editor, window, cx| {
 5691        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5692            s.select_display_ranges([
 5693                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5694                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5695            ])
 5696        });
 5697        editor.duplicate_line_down(&DuplicateLineDown, window, cx);
 5698        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5699        assert_eq!(
 5700            display_ranges(editor, cx),
 5701            vec![
 5702                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(4), 1),
 5703                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(5), 1),
 5704            ]
 5705        );
 5706    });
 5707
 5708    // With `duplicate_line_up` the selections move to the duplicated lines,
 5709    // which are inserted above the original lines
 5710    let editor = cx.add_window(|window, cx| {
 5711        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5712        build_editor(buffer, window, cx)
 5713    });
 5714    _ = editor.update(cx, |editor, window, cx| {
 5715        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5716            s.select_display_ranges([
 5717                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5718                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5719                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 5720                DisplayPoint::new(DisplayRow(3), 0)..DisplayPoint::new(DisplayRow(3), 0),
 5721            ])
 5722        });
 5723        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5724        assert_eq!(editor.display_text(cx), "abc\nabc\ndef\ndef\nghi\n\n");
 5725        assert_eq!(
 5726            display_ranges(editor, cx),
 5727            vec![
 5728                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 5729                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 5730                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 0),
 5731                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 0),
 5732            ]
 5733        );
 5734    });
 5735
 5736    let editor = cx.add_window(|window, cx| {
 5737        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5738        build_editor(buffer, window, cx)
 5739    });
 5740    _ = editor.update(cx, |editor, window, cx| {
 5741        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5742            s.select_display_ranges([
 5743                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5744                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5745            ])
 5746        });
 5747        editor.duplicate_line_up(&DuplicateLineUp, window, cx);
 5748        assert_eq!(editor.display_text(cx), "abc\ndef\nghi\nabc\ndef\nghi\n");
 5749        assert_eq!(
 5750            display_ranges(editor, cx),
 5751            vec![
 5752                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5753                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5754            ]
 5755        );
 5756    });
 5757
 5758    let editor = cx.add_window(|window, cx| {
 5759        let buffer = MultiBuffer::build_simple("abc\ndef\nghi\n", cx);
 5760        build_editor(buffer, window, cx)
 5761    });
 5762    _ = editor.update(cx, |editor, window, cx| {
 5763        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5764            s.select_display_ranges([
 5765                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5766                DisplayPoint::new(DisplayRow(1), 2)..DisplayPoint::new(DisplayRow(2), 1),
 5767            ])
 5768        });
 5769        editor.duplicate_selection(&DuplicateSelection, window, cx);
 5770        assert_eq!(editor.display_text(cx), "abc\ndbc\ndef\ngf\nghi\n");
 5771        assert_eq!(
 5772            display_ranges(editor, cx),
 5773            vec![
 5774                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5775                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 1),
 5776            ]
 5777        );
 5778    });
 5779}
 5780
 5781#[gpui::test]
 5782fn test_move_line_up_down(cx: &mut TestAppContext) {
 5783    init_test(cx, |_| {});
 5784
 5785    let editor = cx.add_window(|window, cx| {
 5786        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5787        build_editor(buffer, window, cx)
 5788    });
 5789    _ = editor.update(cx, |editor, window, cx| {
 5790        editor.fold_creases(
 5791            vec![
 5792                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 5793                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 5794                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 5795            ],
 5796            true,
 5797            window,
 5798            cx,
 5799        );
 5800        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5801            s.select_display_ranges([
 5802                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5803                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5804                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5805                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2),
 5806            ])
 5807        });
 5808        assert_eq!(
 5809            editor.display_text(cx),
 5810            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i\njjjjj"
 5811        );
 5812
 5813        editor.move_line_up(&MoveLineUp, window, cx);
 5814        assert_eq!(
 5815            editor.display_text(cx),
 5816            "aa⋯bbb\nccc⋯eeee\nggggg\n⋯i\njjjjj\nfffff"
 5817        );
 5818        assert_eq!(
 5819            display_ranges(editor, cx),
 5820            vec![
 5821                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
 5822                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5823                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5824                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5825            ]
 5826        );
 5827    });
 5828
 5829    _ = editor.update(cx, |editor, window, cx| {
 5830        editor.move_line_down(&MoveLineDown, window, cx);
 5831        assert_eq!(
 5832            editor.display_text(cx),
 5833            "ccc⋯eeee\naa⋯bbb\nfffff\nggggg\n⋯i\njjjjj"
 5834        );
 5835        assert_eq!(
 5836            display_ranges(editor, cx),
 5837            vec![
 5838                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5839                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5840                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5841                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5842            ]
 5843        );
 5844    });
 5845
 5846    _ = editor.update(cx, |editor, window, cx| {
 5847        editor.move_line_down(&MoveLineDown, window, cx);
 5848        assert_eq!(
 5849            editor.display_text(cx),
 5850            "ccc⋯eeee\nfffff\naa⋯bbb\nggggg\n⋯i\njjjjj"
 5851        );
 5852        assert_eq!(
 5853            display_ranges(editor, cx),
 5854            vec![
 5855                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5856                DisplayPoint::new(DisplayRow(3), 1)..DisplayPoint::new(DisplayRow(3), 1),
 5857                DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(4), 3),
 5858                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(5), 2)
 5859            ]
 5860        );
 5861    });
 5862
 5863    _ = editor.update(cx, |editor, window, cx| {
 5864        editor.move_line_up(&MoveLineUp, window, cx);
 5865        assert_eq!(
 5866            editor.display_text(cx),
 5867            "ccc⋯eeee\naa⋯bbb\nggggg\n⋯i\njjjjj\nfffff"
 5868        );
 5869        assert_eq!(
 5870            display_ranges(editor, cx),
 5871            vec![
 5872                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
 5873                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1),
 5874                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(3), 3),
 5875                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(4), 2)
 5876            ]
 5877        );
 5878    });
 5879}
 5880
 5881#[gpui::test]
 5882fn test_move_line_up_selection_at_end_of_fold(cx: &mut TestAppContext) {
 5883    init_test(cx, |_| {});
 5884    let editor = cx.add_window(|window, cx| {
 5885        let buffer = MultiBuffer::build_simple("\n\n\n\n\n\naaaa\nbbbb\ncccc", cx);
 5886        build_editor(buffer, window, cx)
 5887    });
 5888    _ = editor.update(cx, |editor, window, cx| {
 5889        editor.fold_creases(
 5890            vec![Crease::simple(
 5891                Point::new(6, 4)..Point::new(7, 4),
 5892                FoldPlaceholder::test(),
 5893            )],
 5894            true,
 5895            window,
 5896            cx,
 5897        );
 5898        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5899            s.select_ranges([Point::new(7, 4)..Point::new(7, 4)])
 5900        });
 5901        assert_eq!(editor.display_text(cx), "\n\n\n\n\n\naaaa⋯\ncccc");
 5902        editor.move_line_up(&MoveLineUp, window, cx);
 5903        let buffer_text = editor.buffer.read(cx).snapshot(cx).text();
 5904        assert_eq!(buffer_text, "\n\n\n\n\naaaa\nbbbb\n\ncccc");
 5905    });
 5906}
 5907
 5908#[gpui::test]
 5909fn test_move_line_up_down_with_blocks(cx: &mut TestAppContext) {
 5910    init_test(cx, |_| {});
 5911
 5912    let editor = cx.add_window(|window, cx| {
 5913        let buffer = MultiBuffer::build_simple(&sample_text(10, 5, 'a'), cx);
 5914        build_editor(buffer, window, cx)
 5915    });
 5916    _ = editor.update(cx, |editor, window, cx| {
 5917        let snapshot = editor.buffer.read(cx).snapshot(cx);
 5918        editor.insert_blocks(
 5919            [BlockProperties {
 5920                style: BlockStyle::Fixed,
 5921                placement: BlockPlacement::Below(snapshot.anchor_after(Point::new(2, 0))),
 5922                height: Some(1),
 5923                render: Arc::new(|_| div().into_any()),
 5924                priority: 0,
 5925            }],
 5926            Some(Autoscroll::fit()),
 5927            cx,
 5928        );
 5929        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 5930            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
 5931        });
 5932        editor.move_line_down(&MoveLineDown, window, cx);
 5933    });
 5934}
 5935
 5936#[gpui::test]
 5937async fn test_selections_and_replace_blocks(cx: &mut TestAppContext) {
 5938    init_test(cx, |_| {});
 5939
 5940    let mut cx = EditorTestContext::new(cx).await;
 5941    cx.set_state(
 5942        &"
 5943            ˇzero
 5944            one
 5945            two
 5946            three
 5947            four
 5948            five
 5949        "
 5950        .unindent(),
 5951    );
 5952
 5953    // Create a four-line block that replaces three lines of text.
 5954    cx.update_editor(|editor, window, cx| {
 5955        let snapshot = editor.snapshot(window, cx);
 5956        let snapshot = &snapshot.buffer_snapshot();
 5957        let placement = BlockPlacement::Replace(
 5958            snapshot.anchor_after(Point::new(1, 0))..=snapshot.anchor_after(Point::new(3, 0)),
 5959        );
 5960        editor.insert_blocks(
 5961            [BlockProperties {
 5962                placement,
 5963                height: Some(4),
 5964                style: BlockStyle::Sticky,
 5965                render: Arc::new(|_| gpui::div().into_any_element()),
 5966                priority: 0,
 5967            }],
 5968            None,
 5969            cx,
 5970        );
 5971    });
 5972
 5973    // Move down so that the cursor touches the block.
 5974    cx.update_editor(|editor, window, cx| {
 5975        editor.move_down(&Default::default(), window, cx);
 5976    });
 5977    cx.assert_editor_state(
 5978        &"
 5979            zero
 5980            «one
 5981            two
 5982            threeˇ»
 5983            four
 5984            five
 5985        "
 5986        .unindent(),
 5987    );
 5988
 5989    // Move down past the block.
 5990    cx.update_editor(|editor, window, cx| {
 5991        editor.move_down(&Default::default(), window, cx);
 5992    });
 5993    cx.assert_editor_state(
 5994        &"
 5995            zero
 5996            one
 5997            two
 5998            three
 5999            ˇfour
 6000            five
 6001        "
 6002        .unindent(),
 6003    );
 6004}
 6005
 6006#[gpui::test]
 6007fn test_transpose(cx: &mut TestAppContext) {
 6008    init_test(cx, |_| {});
 6009
 6010    _ = cx.add_window(|window, cx| {
 6011        let mut editor = build_editor(MultiBuffer::build_simple("abc", cx), window, cx);
 6012        editor.set_style(EditorStyle::default(), window, cx);
 6013        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6014            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
 6015        });
 6016        editor.transpose(&Default::default(), window, cx);
 6017        assert_eq!(editor.text(cx), "bac");
 6018        assert_eq!(
 6019            editor.selections.ranges(&editor.display_snapshot(cx)),
 6020            [MultiBufferOffset(2)..MultiBufferOffset(2)]
 6021        );
 6022
 6023        editor.transpose(&Default::default(), window, cx);
 6024        assert_eq!(editor.text(cx), "bca");
 6025        assert_eq!(
 6026            editor.selections.ranges(&editor.display_snapshot(cx)),
 6027            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6028        );
 6029
 6030        editor.transpose(&Default::default(), window, cx);
 6031        assert_eq!(editor.text(cx), "bac");
 6032        assert_eq!(
 6033            editor.selections.ranges(&editor.display_snapshot(cx)),
 6034            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6035        );
 6036
 6037        editor
 6038    });
 6039
 6040    _ = cx.add_window(|window, cx| {
 6041        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6042        editor.set_style(EditorStyle::default(), window, cx);
 6043        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6044            s.select_ranges([MultiBufferOffset(3)..MultiBufferOffset(3)])
 6045        });
 6046        editor.transpose(&Default::default(), window, cx);
 6047        assert_eq!(editor.text(cx), "acb\nde");
 6048        assert_eq!(
 6049            editor.selections.ranges(&editor.display_snapshot(cx)),
 6050            [MultiBufferOffset(3)..MultiBufferOffset(3)]
 6051        );
 6052
 6053        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6054            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6055        });
 6056        editor.transpose(&Default::default(), window, cx);
 6057        assert_eq!(editor.text(cx), "acbd\ne");
 6058        assert_eq!(
 6059            editor.selections.ranges(&editor.display_snapshot(cx)),
 6060            [MultiBufferOffset(5)..MultiBufferOffset(5)]
 6061        );
 6062
 6063        editor.transpose(&Default::default(), window, cx);
 6064        assert_eq!(editor.text(cx), "acbde\n");
 6065        assert_eq!(
 6066            editor.selections.ranges(&editor.display_snapshot(cx)),
 6067            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6068        );
 6069
 6070        editor.transpose(&Default::default(), window, cx);
 6071        assert_eq!(editor.text(cx), "acbd\ne");
 6072        assert_eq!(
 6073            editor.selections.ranges(&editor.display_snapshot(cx)),
 6074            [MultiBufferOffset(6)..MultiBufferOffset(6)]
 6075        );
 6076
 6077        editor
 6078    });
 6079
 6080    _ = cx.add_window(|window, cx| {
 6081        let mut editor = build_editor(MultiBuffer::build_simple("abc\nde", cx), window, cx);
 6082        editor.set_style(EditorStyle::default(), window, cx);
 6083        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6084            s.select_ranges([
 6085                MultiBufferOffset(1)..MultiBufferOffset(1),
 6086                MultiBufferOffset(2)..MultiBufferOffset(2),
 6087                MultiBufferOffset(4)..MultiBufferOffset(4),
 6088            ])
 6089        });
 6090        editor.transpose(&Default::default(), window, cx);
 6091        assert_eq!(editor.text(cx), "bacd\ne");
 6092        assert_eq!(
 6093            editor.selections.ranges(&editor.display_snapshot(cx)),
 6094            [
 6095                MultiBufferOffset(2)..MultiBufferOffset(2),
 6096                MultiBufferOffset(3)..MultiBufferOffset(3),
 6097                MultiBufferOffset(5)..MultiBufferOffset(5)
 6098            ]
 6099        );
 6100
 6101        editor.transpose(&Default::default(), window, cx);
 6102        assert_eq!(editor.text(cx), "bcade\n");
 6103        assert_eq!(
 6104            editor.selections.ranges(&editor.display_snapshot(cx)),
 6105            [
 6106                MultiBufferOffset(3)..MultiBufferOffset(3),
 6107                MultiBufferOffset(4)..MultiBufferOffset(4),
 6108                MultiBufferOffset(6)..MultiBufferOffset(6)
 6109            ]
 6110        );
 6111
 6112        editor.transpose(&Default::default(), window, cx);
 6113        assert_eq!(editor.text(cx), "bcda\ne");
 6114        assert_eq!(
 6115            editor.selections.ranges(&editor.display_snapshot(cx)),
 6116            [
 6117                MultiBufferOffset(4)..MultiBufferOffset(4),
 6118                MultiBufferOffset(6)..MultiBufferOffset(6)
 6119            ]
 6120        );
 6121
 6122        editor.transpose(&Default::default(), window, cx);
 6123        assert_eq!(editor.text(cx), "bcade\n");
 6124        assert_eq!(
 6125            editor.selections.ranges(&editor.display_snapshot(cx)),
 6126            [
 6127                MultiBufferOffset(4)..MultiBufferOffset(4),
 6128                MultiBufferOffset(6)..MultiBufferOffset(6)
 6129            ]
 6130        );
 6131
 6132        editor.transpose(&Default::default(), window, cx);
 6133        assert_eq!(editor.text(cx), "bcaed\n");
 6134        assert_eq!(
 6135            editor.selections.ranges(&editor.display_snapshot(cx)),
 6136            [
 6137                MultiBufferOffset(5)..MultiBufferOffset(5),
 6138                MultiBufferOffset(6)..MultiBufferOffset(6)
 6139            ]
 6140        );
 6141
 6142        editor
 6143    });
 6144
 6145    _ = cx.add_window(|window, cx| {
 6146        let mut editor = build_editor(MultiBuffer::build_simple("🍐🏀✋", cx), window, cx);
 6147        editor.set_style(EditorStyle::default(), window, cx);
 6148        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 6149            s.select_ranges([MultiBufferOffset(4)..MultiBufferOffset(4)])
 6150        });
 6151        editor.transpose(&Default::default(), window, cx);
 6152        assert_eq!(editor.text(cx), "🏀🍐✋");
 6153        assert_eq!(
 6154            editor.selections.ranges(&editor.display_snapshot(cx)),
 6155            [MultiBufferOffset(8)..MultiBufferOffset(8)]
 6156        );
 6157
 6158        editor.transpose(&Default::default(), window, cx);
 6159        assert_eq!(editor.text(cx), "🏀✋🍐");
 6160        assert_eq!(
 6161            editor.selections.ranges(&editor.display_snapshot(cx)),
 6162            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6163        );
 6164
 6165        editor.transpose(&Default::default(), window, cx);
 6166        assert_eq!(editor.text(cx), "🏀🍐✋");
 6167        assert_eq!(
 6168            editor.selections.ranges(&editor.display_snapshot(cx)),
 6169            [MultiBufferOffset(11)..MultiBufferOffset(11)]
 6170        );
 6171
 6172        editor
 6173    });
 6174}
 6175
 6176#[gpui::test]
 6177async fn test_rewrap(cx: &mut TestAppContext) {
 6178    init_test(cx, |settings| {
 6179        settings.languages.0.extend([
 6180            (
 6181                "Markdown".into(),
 6182                LanguageSettingsContent {
 6183                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6184                    preferred_line_length: Some(40),
 6185                    ..Default::default()
 6186                },
 6187            ),
 6188            (
 6189                "Plain Text".into(),
 6190                LanguageSettingsContent {
 6191                    allow_rewrap: Some(language_settings::RewrapBehavior::Anywhere),
 6192                    preferred_line_length: Some(40),
 6193                    ..Default::default()
 6194                },
 6195            ),
 6196            (
 6197                "C++".into(),
 6198                LanguageSettingsContent {
 6199                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6200                    preferred_line_length: Some(40),
 6201                    ..Default::default()
 6202                },
 6203            ),
 6204            (
 6205                "Python".into(),
 6206                LanguageSettingsContent {
 6207                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6208                    preferred_line_length: Some(40),
 6209                    ..Default::default()
 6210                },
 6211            ),
 6212            (
 6213                "Rust".into(),
 6214                LanguageSettingsContent {
 6215                    allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6216                    preferred_line_length: Some(40),
 6217                    ..Default::default()
 6218                },
 6219            ),
 6220        ])
 6221    });
 6222
 6223    let mut cx = EditorTestContext::new(cx).await;
 6224
 6225    let cpp_language = Arc::new(Language::new(
 6226        LanguageConfig {
 6227            name: "C++".into(),
 6228            line_comments: vec!["// ".into()],
 6229            ..LanguageConfig::default()
 6230        },
 6231        None,
 6232    ));
 6233    let python_language = Arc::new(Language::new(
 6234        LanguageConfig {
 6235            name: "Python".into(),
 6236            line_comments: vec!["# ".into()],
 6237            ..LanguageConfig::default()
 6238        },
 6239        None,
 6240    ));
 6241    let markdown_language = Arc::new(Language::new(
 6242        LanguageConfig {
 6243            name: "Markdown".into(),
 6244            rewrap_prefixes: vec![
 6245                regex::Regex::new("\\d+\\.\\s+").unwrap(),
 6246                regex::Regex::new("[-*+]\\s+").unwrap(),
 6247            ],
 6248            ..LanguageConfig::default()
 6249        },
 6250        None,
 6251    ));
 6252    let rust_language = Arc::new(
 6253        Language::new(
 6254            LanguageConfig {
 6255                name: "Rust".into(),
 6256                line_comments: vec!["// ".into(), "/// ".into()],
 6257                ..LanguageConfig::default()
 6258            },
 6259            Some(tree_sitter_rust::LANGUAGE.into()),
 6260        )
 6261        .with_override_query("[(line_comment)(block_comment)] @comment.inclusive")
 6262        .unwrap(),
 6263    );
 6264
 6265    let plaintext_language = Arc::new(Language::new(
 6266        LanguageConfig {
 6267            name: "Plain Text".into(),
 6268            ..LanguageConfig::default()
 6269        },
 6270        None,
 6271    ));
 6272
 6273    // Test basic rewrapping of a long line with a cursor
 6274    assert_rewrap(
 6275        indoc! {"
 6276            // ˇThis is a long comment that needs to be wrapped.
 6277        "},
 6278        indoc! {"
 6279            // ˇThis is a long comment that needs to
 6280            // be wrapped.
 6281        "},
 6282        cpp_language.clone(),
 6283        &mut cx,
 6284    );
 6285
 6286    // Test rewrapping a full selection
 6287    assert_rewrap(
 6288        indoc! {"
 6289            «// This selected long comment needs to be wrapped.ˇ»"
 6290        },
 6291        indoc! {"
 6292            «// This selected long comment needs to
 6293            // be wrapped.ˇ»"
 6294        },
 6295        cpp_language.clone(),
 6296        &mut cx,
 6297    );
 6298
 6299    // Test multiple cursors on different lines within the same paragraph are preserved after rewrapping
 6300    assert_rewrap(
 6301        indoc! {"
 6302            // ˇThis is the first line.
 6303            // Thisˇ is the second line.
 6304            // This is the thirdˇ line, all part of one paragraph.
 6305         "},
 6306        indoc! {"
 6307            // ˇThis is the first line. Thisˇ is the
 6308            // second line. This is the thirdˇ line,
 6309            // all part of one paragraph.
 6310         "},
 6311        cpp_language.clone(),
 6312        &mut cx,
 6313    );
 6314
 6315    // Test multiple cursors in different paragraphs trigger separate rewraps
 6316    assert_rewrap(
 6317        indoc! {"
 6318            // ˇThis is the first paragraph, first line.
 6319            // ˇThis is the first paragraph, second line.
 6320
 6321            // ˇThis is the second paragraph, first line.
 6322            // ˇThis is the second paragraph, second line.
 6323        "},
 6324        indoc! {"
 6325            // ˇThis is the first paragraph, first
 6326            // line. ˇThis is the first paragraph,
 6327            // second line.
 6328
 6329            // ˇThis is the second paragraph, first
 6330            // line. ˇThis is the second paragraph,
 6331            // second line.
 6332        "},
 6333        cpp_language.clone(),
 6334        &mut cx,
 6335    );
 6336
 6337    // Test that change in comment prefix (e.g., `//` to `///`) trigger seperate rewraps
 6338    assert_rewrap(
 6339        indoc! {"
 6340            «// A regular long long comment to be wrapped.
 6341            /// A documentation long comment to be wrapped.ˇ»
 6342          "},
 6343        indoc! {"
 6344            «// A regular long long comment to be
 6345            // wrapped.
 6346            /// A documentation long comment to be
 6347            /// wrapped.ˇ»
 6348          "},
 6349        rust_language.clone(),
 6350        &mut cx,
 6351    );
 6352
 6353    // Test that change in indentation level trigger seperate rewraps
 6354    assert_rewrap(
 6355        indoc! {"
 6356            fn foo() {
 6357                «// This is a long comment at the base indent.
 6358                    // This is a long comment at the next indent.ˇ»
 6359            }
 6360        "},
 6361        indoc! {"
 6362            fn foo() {
 6363                «// This is a long comment at the
 6364                // base indent.
 6365                    // This is a long comment at the
 6366                    // next indent.ˇ»
 6367            }
 6368        "},
 6369        rust_language.clone(),
 6370        &mut cx,
 6371    );
 6372
 6373    // Test that different comment prefix characters (e.g., '#') are handled correctly
 6374    assert_rewrap(
 6375        indoc! {"
 6376            # ˇThis is a long comment using a pound sign.
 6377        "},
 6378        indoc! {"
 6379            # ˇThis is a long comment using a pound
 6380            # sign.
 6381        "},
 6382        python_language,
 6383        &mut cx,
 6384    );
 6385
 6386    // Test rewrapping only affects comments, not code even when selected
 6387    assert_rewrap(
 6388        indoc! {"
 6389            «/// This doc comment is long and should be wrapped.
 6390            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6391        "},
 6392        indoc! {"
 6393            «/// This doc comment is long and should
 6394            /// be wrapped.
 6395            fn my_func(a: u32, b: u32, c: u32, d: u32, e: u32, f: u32) {}ˇ»
 6396        "},
 6397        rust_language.clone(),
 6398        &mut cx,
 6399    );
 6400
 6401    // Test that rewrapping works in Markdown documents where `allow_rewrap` is `Anywhere`
 6402    assert_rewrap(
 6403        indoc! {"
 6404            # Header
 6405
 6406            A long long long line of markdown text to wrap.ˇ
 6407         "},
 6408        indoc! {"
 6409            # Header
 6410
 6411            A long long long line of markdown text
 6412            to wrap.ˇ
 6413         "},
 6414        markdown_language.clone(),
 6415        &mut cx,
 6416    );
 6417
 6418    // Test that rewrapping boundary works and preserves relative indent for Markdown documents
 6419    assert_rewrap(
 6420        indoc! {"
 6421            «1. This is a numbered list item that is very long and needs to be wrapped properly.
 6422            2. This is a numbered list item that is very long and needs to be wrapped properly.
 6423            - This is an unordered list item that is also very long and should not merge with the numbered item.ˇ»
 6424        "},
 6425        indoc! {"
 6426            «1. This is a numbered list item that is
 6427               very long and needs to be wrapped
 6428               properly.
 6429            2. This is a numbered list item that is
 6430               very long and needs to be wrapped
 6431               properly.
 6432            - This is an unordered list item that is
 6433              also very long and should not merge
 6434              with the numbered item.ˇ»
 6435        "},
 6436        markdown_language.clone(),
 6437        &mut cx,
 6438    );
 6439
 6440    // Test that rewrapping add indents for rewrapping boundary if not exists already.
 6441    assert_rewrap(
 6442        indoc! {"
 6443            «1. This is a numbered list item that is
 6444            very long and needs to be wrapped
 6445            properly.
 6446            2. This is a numbered list item that is
 6447            very long and needs to be wrapped
 6448            properly.
 6449            - This is an unordered list item that is
 6450            also very long and should not merge with
 6451            the numbered item.ˇ»
 6452        "},
 6453        indoc! {"
 6454            «1. This is a numbered list item that is
 6455               very long and needs to be wrapped
 6456               properly.
 6457            2. This is a numbered list item that is
 6458               very long and needs to be wrapped
 6459               properly.
 6460            - This is an unordered list item that is
 6461              also very long and should not merge
 6462              with the numbered item.ˇ»
 6463        "},
 6464        markdown_language.clone(),
 6465        &mut cx,
 6466    );
 6467
 6468    // Test that rewrapping maintain indents even when they already exists.
 6469    assert_rewrap(
 6470        indoc! {"
 6471            «1. This is a numbered list
 6472               item that is very long and needs to be wrapped properly.
 6473            2. This is a numbered list
 6474               item that is very long and needs to be wrapped properly.
 6475            - This is an unordered list item that is also very long and
 6476              should not merge with the numbered item.ˇ»
 6477        "},
 6478        indoc! {"
 6479            «1. This is a numbered list item that is
 6480               very long and needs to be wrapped
 6481               properly.
 6482            2. This is a numbered list item that is
 6483               very long and needs to be wrapped
 6484               properly.
 6485            - This is an unordered list item that is
 6486              also very long and should not merge
 6487              with the numbered item.ˇ»
 6488        "},
 6489        markdown_language,
 6490        &mut cx,
 6491    );
 6492
 6493    // Test that rewrapping works in plain text where `allow_rewrap` is `Anywhere`
 6494    assert_rewrap(
 6495        indoc! {"
 6496            ˇThis is a very long line of plain text that will be wrapped.
 6497        "},
 6498        indoc! {"
 6499            ˇThis is a very long line of plain text
 6500            that will be wrapped.
 6501        "},
 6502        plaintext_language.clone(),
 6503        &mut cx,
 6504    );
 6505
 6506    // Test that non-commented code acts as a paragraph boundary within a selection
 6507    assert_rewrap(
 6508        indoc! {"
 6509               «// This is the first long comment block to be wrapped.
 6510               fn my_func(a: u32);
 6511               // This is the second long comment block to be wrapped.ˇ»
 6512           "},
 6513        indoc! {"
 6514               «// This is the first long comment block
 6515               // to be wrapped.
 6516               fn my_func(a: u32);
 6517               // This is the second long comment block
 6518               // to be wrapped.ˇ»
 6519           "},
 6520        rust_language,
 6521        &mut cx,
 6522    );
 6523
 6524    // Test rewrapping multiple selections, including ones with blank lines or tabs
 6525    assert_rewrap(
 6526        indoc! {"
 6527            «ˇThis is a very long line that will be wrapped.
 6528
 6529            This is another paragraph in the same selection.»
 6530
 6531            «\tThis is a very long indented line that will be wrapped.ˇ»
 6532         "},
 6533        indoc! {"
 6534            «ˇThis is a very long line that will be
 6535            wrapped.
 6536
 6537            This is another paragraph in the same
 6538            selection.»
 6539
 6540            «\tThis is a very long indented line
 6541            \tthat will be wrapped.ˇ»
 6542         "},
 6543        plaintext_language,
 6544        &mut cx,
 6545    );
 6546
 6547    // Test that an empty comment line acts as a paragraph boundary
 6548    assert_rewrap(
 6549        indoc! {"
 6550            // ˇThis is a long comment that will be wrapped.
 6551            //
 6552            // And this is another long comment that will also be wrapped.ˇ
 6553         "},
 6554        indoc! {"
 6555            // ˇThis is a long comment that will be
 6556            // wrapped.
 6557            //
 6558            // And this is another long comment that
 6559            // will also be wrapped.ˇ
 6560         "},
 6561        cpp_language,
 6562        &mut cx,
 6563    );
 6564
 6565    #[track_caller]
 6566    fn assert_rewrap(
 6567        unwrapped_text: &str,
 6568        wrapped_text: &str,
 6569        language: Arc<Language>,
 6570        cx: &mut EditorTestContext,
 6571    ) {
 6572        cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
 6573        cx.set_state(unwrapped_text);
 6574        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6575        cx.assert_editor_state(wrapped_text);
 6576    }
 6577}
 6578
 6579#[gpui::test]
 6580async fn test_rewrap_block_comments(cx: &mut TestAppContext) {
 6581    init_test(cx, |settings| {
 6582        settings.languages.0.extend([(
 6583            "Rust".into(),
 6584            LanguageSettingsContent {
 6585                allow_rewrap: Some(language_settings::RewrapBehavior::InComments),
 6586                preferred_line_length: Some(40),
 6587                ..Default::default()
 6588            },
 6589        )])
 6590    });
 6591
 6592    let mut cx = EditorTestContext::new(cx).await;
 6593
 6594    let rust_lang = Arc::new(
 6595        Language::new(
 6596            LanguageConfig {
 6597                name: "Rust".into(),
 6598                line_comments: vec!["// ".into()],
 6599                block_comment: Some(BlockCommentConfig {
 6600                    start: "/*".into(),
 6601                    end: "*/".into(),
 6602                    prefix: "* ".into(),
 6603                    tab_size: 1,
 6604                }),
 6605                documentation_comment: Some(BlockCommentConfig {
 6606                    start: "/**".into(),
 6607                    end: "*/".into(),
 6608                    prefix: "* ".into(),
 6609                    tab_size: 1,
 6610                }),
 6611
 6612                ..LanguageConfig::default()
 6613            },
 6614            Some(tree_sitter_rust::LANGUAGE.into()),
 6615        )
 6616        .with_override_query("[(line_comment) (block_comment)] @comment.inclusive")
 6617        .unwrap(),
 6618    );
 6619
 6620    // regular block comment
 6621    assert_rewrap(
 6622        indoc! {"
 6623            /*
 6624             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6625             */
 6626            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6627        "},
 6628        indoc! {"
 6629            /*
 6630             *ˇ Lorem ipsum dolor sit amet,
 6631             * consectetur adipiscing elit.
 6632             */
 6633            /*
 6634             *ˇ Lorem ipsum dolor sit amet,
 6635             * consectetur adipiscing elit.
 6636             */
 6637        "},
 6638        rust_lang.clone(),
 6639        &mut cx,
 6640    );
 6641
 6642    // indent is respected
 6643    assert_rewrap(
 6644        indoc! {"
 6645            {}
 6646                /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6647        "},
 6648        indoc! {"
 6649            {}
 6650                /*
 6651                 *ˇ Lorem ipsum dolor sit amet,
 6652                 * consectetur adipiscing elit.
 6653                 */
 6654        "},
 6655        rust_lang.clone(),
 6656        &mut cx,
 6657    );
 6658
 6659    // short block comments with inline delimiters
 6660    assert_rewrap(
 6661        indoc! {"
 6662            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6663            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6664             */
 6665            /*
 6666             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6667        "},
 6668        indoc! {"
 6669            /*
 6670             *ˇ Lorem ipsum dolor sit amet,
 6671             * consectetur adipiscing elit.
 6672             */
 6673            /*
 6674             *ˇ Lorem ipsum dolor sit amet,
 6675             * consectetur adipiscing elit.
 6676             */
 6677            /*
 6678             *ˇ Lorem ipsum dolor sit amet,
 6679             * consectetur adipiscing elit.
 6680             */
 6681        "},
 6682        rust_lang.clone(),
 6683        &mut cx,
 6684    );
 6685
 6686    // multiline block comment with inline start/end delimiters
 6687    assert_rewrap(
 6688        indoc! {"
 6689            /*ˇ Lorem ipsum dolor sit amet,
 6690             * consectetur adipiscing elit. */
 6691        "},
 6692        indoc! {"
 6693            /*
 6694             *ˇ Lorem ipsum dolor sit amet,
 6695             * consectetur adipiscing elit.
 6696             */
 6697        "},
 6698        rust_lang.clone(),
 6699        &mut cx,
 6700    );
 6701
 6702    // block comment rewrap still respects paragraph bounds
 6703    assert_rewrap(
 6704        indoc! {"
 6705            /*
 6706             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6707             *
 6708             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6709             */
 6710        "},
 6711        indoc! {"
 6712            /*
 6713             *ˇ Lorem ipsum dolor sit amet,
 6714             * consectetur adipiscing elit.
 6715             *
 6716             * Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6717             */
 6718        "},
 6719        rust_lang.clone(),
 6720        &mut cx,
 6721    );
 6722
 6723    // documentation comments
 6724    assert_rewrap(
 6725        indoc! {"
 6726            /**ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6727            /**
 6728             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6729             */
 6730        "},
 6731        indoc! {"
 6732            /**
 6733             *ˇ Lorem ipsum dolor sit amet,
 6734             * consectetur adipiscing elit.
 6735             */
 6736            /**
 6737             *ˇ Lorem ipsum dolor sit amet,
 6738             * consectetur adipiscing elit.
 6739             */
 6740        "},
 6741        rust_lang.clone(),
 6742        &mut cx,
 6743    );
 6744
 6745    // different, adjacent comments
 6746    assert_rewrap(
 6747        indoc! {"
 6748            /**
 6749             *ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6750             */
 6751            /*ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6752            //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6753        "},
 6754        indoc! {"
 6755            /**
 6756             *ˇ Lorem ipsum dolor sit amet,
 6757             * consectetur adipiscing elit.
 6758             */
 6759            /*
 6760             *ˇ Lorem ipsum dolor sit amet,
 6761             * consectetur adipiscing elit.
 6762             */
 6763            //ˇ Lorem ipsum dolor sit amet,
 6764            // consectetur adipiscing elit.
 6765        "},
 6766        rust_lang.clone(),
 6767        &mut cx,
 6768    );
 6769
 6770    // selection w/ single short block comment
 6771    assert_rewrap(
 6772        indoc! {"
 6773            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6774        "},
 6775        indoc! {"
 6776            «/*
 6777             * Lorem ipsum dolor sit amet,
 6778             * consectetur adipiscing elit.
 6779             */ˇ»
 6780        "},
 6781        rust_lang.clone(),
 6782        &mut cx,
 6783    );
 6784
 6785    // rewrapping a single comment w/ abutting comments
 6786    assert_rewrap(
 6787        indoc! {"
 6788            /* ˇLorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6789            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6790        "},
 6791        indoc! {"
 6792            /*
 6793             * ˇLorem ipsum dolor sit amet,
 6794             * consectetur adipiscing elit.
 6795             */
 6796            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6797        "},
 6798        rust_lang.clone(),
 6799        &mut cx,
 6800    );
 6801
 6802    // selection w/ non-abutting short block comments
 6803    assert_rewrap(
 6804        indoc! {"
 6805            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6806
 6807            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6808        "},
 6809        indoc! {"
 6810            «/*
 6811             * Lorem ipsum dolor sit amet,
 6812             * consectetur adipiscing elit.
 6813             */
 6814
 6815            /*
 6816             * Lorem ipsum dolor sit amet,
 6817             * consectetur adipiscing elit.
 6818             */ˇ»
 6819        "},
 6820        rust_lang.clone(),
 6821        &mut cx,
 6822    );
 6823
 6824    // selection of multiline block comments
 6825    assert_rewrap(
 6826        indoc! {"
 6827            «/* Lorem ipsum dolor sit amet,
 6828             * consectetur adipiscing elit. */ˇ»
 6829        "},
 6830        indoc! {"
 6831            «/*
 6832             * Lorem ipsum dolor sit amet,
 6833             * consectetur adipiscing elit.
 6834             */ˇ»
 6835        "},
 6836        rust_lang.clone(),
 6837        &mut cx,
 6838    );
 6839
 6840    // partial selection of multiline block comments
 6841    assert_rewrap(
 6842        indoc! {"
 6843            «/* Lorem ipsum dolor sit amet,ˇ»
 6844             * consectetur adipiscing elit. */
 6845            /* Lorem ipsum dolor sit amet,
 6846             «* consectetur adipiscing elit. */ˇ»
 6847        "},
 6848        indoc! {"
 6849            «/*
 6850             * Lorem ipsum dolor sit amet,ˇ»
 6851             * consectetur adipiscing elit. */
 6852            /* Lorem ipsum dolor sit amet,
 6853             «* consectetur adipiscing elit.
 6854             */ˇ»
 6855        "},
 6856        rust_lang.clone(),
 6857        &mut cx,
 6858    );
 6859
 6860    // selection w/ abutting short block comments
 6861    // TODO: should not be combined; should rewrap as 2 comments
 6862    assert_rewrap(
 6863        indoc! {"
 6864            «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6865            /* Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6866        "},
 6867        // desired behavior:
 6868        // indoc! {"
 6869        //     «/*
 6870        //      * Lorem ipsum dolor sit amet,
 6871        //      * consectetur adipiscing elit.
 6872        //      */
 6873        //     /*
 6874        //      * Lorem ipsum dolor sit amet,
 6875        //      * consectetur adipiscing elit.
 6876        //      */ˇ»
 6877        // "},
 6878        // actual behaviour:
 6879        indoc! {"
 6880            «/*
 6881             * Lorem ipsum dolor sit amet,
 6882             * consectetur adipiscing elit. Lorem
 6883             * ipsum dolor sit amet, consectetur
 6884             * adipiscing elit.
 6885             */ˇ»
 6886        "},
 6887        rust_lang.clone(),
 6888        &mut cx,
 6889    );
 6890
 6891    // TODO: same as above, but with delimiters on separate line
 6892    // assert_rewrap(
 6893    //     indoc! {"
 6894    //         «/* Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6895    //          */
 6896    //         /*
 6897    //          * Lorem ipsum dolor sit amet, consectetur adipiscing elit. */ˇ»
 6898    //     "},
 6899    //     // desired:
 6900    //     // indoc! {"
 6901    //     //     «/*
 6902    //     //      * Lorem ipsum dolor sit amet,
 6903    //     //      * consectetur adipiscing elit.
 6904    //     //      */
 6905    //     //     /*
 6906    //     //      * Lorem ipsum dolor sit amet,
 6907    //     //      * consectetur adipiscing elit.
 6908    //     //      */ˇ»
 6909    //     // "},
 6910    //     // actual: (but with trailing w/s on the empty lines)
 6911    //     indoc! {"
 6912    //         «/*
 6913    //          * Lorem ipsum dolor sit amet,
 6914    //          * consectetur adipiscing elit.
 6915    //          *
 6916    //          */
 6917    //         /*
 6918    //          *
 6919    //          * Lorem ipsum dolor sit amet,
 6920    //          * consectetur adipiscing elit.
 6921    //          */ˇ»
 6922    //     "},
 6923    //     rust_lang.clone(),
 6924    //     &mut cx,
 6925    // );
 6926
 6927    // TODO these are unhandled edge cases; not correct, just documenting known issues
 6928    assert_rewrap(
 6929        indoc! {"
 6930            /*
 6931             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 6932             */
 6933            /*
 6934             //ˇ Lorem ipsum dolor sit amet, consectetur adipiscing elit. */
 6935            /*ˇ Lorem ipsum dolor sit amet */ /* consectetur adipiscing elit. */
 6936        "},
 6937        // desired:
 6938        // indoc! {"
 6939        //     /*
 6940        //      *ˇ Lorem ipsum dolor sit amet,
 6941        //      * consectetur adipiscing elit.
 6942        //      */
 6943        //     /*
 6944        //      *ˇ Lorem ipsum dolor sit amet,
 6945        //      * consectetur adipiscing elit.
 6946        //      */
 6947        //     /*
 6948        //      *ˇ Lorem ipsum dolor sit amet
 6949        //      */ /* consectetur adipiscing elit. */
 6950        // "},
 6951        // actual:
 6952        indoc! {"
 6953            /*
 6954             //ˇ Lorem ipsum dolor sit amet,
 6955             // consectetur adipiscing elit.
 6956             */
 6957            /*
 6958             * //ˇ Lorem ipsum dolor sit amet,
 6959             * consectetur adipiscing elit.
 6960             */
 6961            /*
 6962             *ˇ Lorem ipsum dolor sit amet */ /*
 6963             * consectetur adipiscing elit.
 6964             */
 6965        "},
 6966        rust_lang,
 6967        &mut cx,
 6968    );
 6969
 6970    #[track_caller]
 6971    fn assert_rewrap(
 6972        unwrapped_text: &str,
 6973        wrapped_text: &str,
 6974        language: Arc<Language>,
 6975        cx: &mut EditorTestContext,
 6976    ) {
 6977        cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
 6978        cx.set_state(unwrapped_text);
 6979        cx.update_editor(|e, window, cx| e.rewrap(&Rewrap, window, cx));
 6980        cx.assert_editor_state(wrapped_text);
 6981    }
 6982}
 6983
 6984#[gpui::test]
 6985async fn test_hard_wrap(cx: &mut TestAppContext) {
 6986    init_test(cx, |_| {});
 6987    let mut cx = EditorTestContext::new(cx).await;
 6988
 6989    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(git_commit_lang()), cx));
 6990    cx.update_editor(|editor, _, cx| {
 6991        editor.set_hard_wrap(Some(14), cx);
 6992    });
 6993
 6994    cx.set_state(indoc!(
 6995        "
 6996        one two three ˇ
 6997        "
 6998    ));
 6999    cx.simulate_input("four");
 7000    cx.run_until_parked();
 7001
 7002    cx.assert_editor_state(indoc!(
 7003        "
 7004        one two three
 7005        fourˇ
 7006        "
 7007    ));
 7008
 7009    cx.update_editor(|editor, window, cx| {
 7010        editor.newline(&Default::default(), window, cx);
 7011    });
 7012    cx.run_until_parked();
 7013    cx.assert_editor_state(indoc!(
 7014        "
 7015        one two three
 7016        four
 7017        ˇ
 7018        "
 7019    ));
 7020
 7021    cx.simulate_input("five");
 7022    cx.run_until_parked();
 7023    cx.assert_editor_state(indoc!(
 7024        "
 7025        one two three
 7026        four
 7027        fiveˇ
 7028        "
 7029    ));
 7030
 7031    cx.update_editor(|editor, window, cx| {
 7032        editor.newline(&Default::default(), window, cx);
 7033    });
 7034    cx.run_until_parked();
 7035    cx.simulate_input("# ");
 7036    cx.run_until_parked();
 7037    cx.assert_editor_state(indoc!(
 7038        "
 7039        one two three
 7040        four
 7041        five
 7042        # ˇ
 7043        "
 7044    ));
 7045
 7046    cx.update_editor(|editor, window, cx| {
 7047        editor.newline(&Default::default(), window, cx);
 7048    });
 7049    cx.run_until_parked();
 7050    cx.assert_editor_state(indoc!(
 7051        "
 7052        one two three
 7053        four
 7054        five
 7055        #\x20
 7056 7057        "
 7058    ));
 7059
 7060    cx.simulate_input(" 6");
 7061    cx.run_until_parked();
 7062    cx.assert_editor_state(indoc!(
 7063        "
 7064        one two three
 7065        four
 7066        five
 7067        #
 7068        # 6ˇ
 7069        "
 7070    ));
 7071}
 7072
 7073#[gpui::test]
 7074async fn test_cut_line_ends(cx: &mut TestAppContext) {
 7075    init_test(cx, |_| {});
 7076
 7077    let mut cx = EditorTestContext::new(cx).await;
 7078
 7079    cx.set_state(indoc! {"The quick brownˇ"});
 7080    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7081    cx.assert_editor_state(indoc! {"The quick brownˇ"});
 7082
 7083    cx.set_state(indoc! {"The emacs foxˇ"});
 7084    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7085    cx.assert_editor_state(indoc! {"The emacs foxˇ"});
 7086
 7087    cx.set_state(indoc! {"
 7088        The quick« brownˇ»
 7089        fox jumps overˇ
 7090        the lazy dog"});
 7091    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7092    cx.assert_editor_state(indoc! {"
 7093        The quickˇ
 7094        ˇthe lazy dog"});
 7095
 7096    cx.set_state(indoc! {"
 7097        The quick« brownˇ»
 7098        fox jumps overˇ
 7099        the lazy dog"});
 7100    cx.update_editor(|e, window, cx| e.cut_to_end_of_line(&CutToEndOfLine::default(), window, cx));
 7101    cx.assert_editor_state(indoc! {"
 7102        The quickˇ
 7103        fox jumps overˇthe lazy dog"});
 7104
 7105    cx.set_state(indoc! {"
 7106        The quick« brownˇ»
 7107        fox jumps overˇ
 7108        the lazy dog"});
 7109    cx.update_editor(|e, window, cx| {
 7110        e.cut_to_end_of_line(
 7111            &CutToEndOfLine {
 7112                stop_at_newlines: true,
 7113            },
 7114            window,
 7115            cx,
 7116        )
 7117    });
 7118    cx.assert_editor_state(indoc! {"
 7119        The quickˇ
 7120        fox jumps overˇ
 7121        the lazy dog"});
 7122
 7123    cx.set_state(indoc! {"
 7124        The quick« brownˇ»
 7125        fox jumps overˇ
 7126        the lazy dog"});
 7127    cx.update_editor(|e, window, cx| e.kill_ring_cut(&KillRingCut, window, cx));
 7128    cx.assert_editor_state(indoc! {"
 7129        The quickˇ
 7130        fox jumps overˇthe lazy dog"});
 7131}
 7132
 7133#[gpui::test]
 7134async fn test_clipboard(cx: &mut TestAppContext) {
 7135    init_test(cx, |_| {});
 7136
 7137    let mut cx = EditorTestContext::new(cx).await;
 7138
 7139    cx.set_state("«one✅ ˇ»two «three ˇ»four «five ˇ»six ");
 7140    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7141    cx.assert_editor_state("ˇtwo ˇfour ˇsix ");
 7142
 7143    // Paste with three cursors. Each cursor pastes one slice of the clipboard text.
 7144    cx.set_state("two ˇfour ˇsix ˇ");
 7145    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7146    cx.assert_editor_state("two one✅ ˇfour three ˇsix five ˇ");
 7147
 7148    // Paste again but with only two cursors. Since the number of cursors doesn't
 7149    // match the number of slices in the clipboard, the entire clipboard text
 7150    // is pasted at each cursor.
 7151    cx.set_state("ˇtwo one✅ four three six five ˇ");
 7152    cx.update_editor(|e, window, cx| {
 7153        e.handle_input("( ", window, cx);
 7154        e.paste(&Paste, window, cx);
 7155        e.handle_input(") ", window, cx);
 7156    });
 7157    cx.assert_editor_state(
 7158        &([
 7159            "( one✅ ",
 7160            "three ",
 7161            "five ) ˇtwo one✅ four three six five ( one✅ ",
 7162            "three ",
 7163            "five ) ˇ",
 7164        ]
 7165        .join("\n")),
 7166    );
 7167
 7168    // Cut with three selections, one of which is full-line.
 7169    cx.set_state(indoc! {"
 7170        1«2ˇ»3
 7171        4ˇ567
 7172        «8ˇ»9"});
 7173    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7174    cx.assert_editor_state(indoc! {"
 7175        1ˇ3
 7176        ˇ9"});
 7177
 7178    // Paste with three selections, noticing how the copied selection that was full-line
 7179    // gets inserted before the second cursor.
 7180    cx.set_state(indoc! {"
 7181        1ˇ3
 7182 7183        «oˇ»ne"});
 7184    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7185    cx.assert_editor_state(indoc! {"
 7186        12ˇ3
 7187        4567
 7188 7189        8ˇne"});
 7190
 7191    // Copy with a single cursor only, which writes the whole line into the clipboard.
 7192    cx.set_state(indoc! {"
 7193        The quick brown
 7194        fox juˇmps over
 7195        the lazy dog"});
 7196    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7197    assert_eq!(
 7198        cx.read_from_clipboard()
 7199            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7200        Some("fox jumps over\n".to_string())
 7201    );
 7202
 7203    // Paste with three selections, noticing how the copied full-line selection is inserted
 7204    // before the empty selections but replaces the selection that is non-empty.
 7205    cx.set_state(indoc! {"
 7206        Tˇhe quick brown
 7207        «foˇ»x jumps over
 7208        tˇhe lazy dog"});
 7209    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7210    cx.assert_editor_state(indoc! {"
 7211        fox jumps over
 7212        Tˇhe quick brown
 7213        fox jumps over
 7214        ˇx jumps over
 7215        fox jumps over
 7216        tˇhe lazy dog"});
 7217}
 7218
 7219#[gpui::test]
 7220async fn test_copy_trim(cx: &mut TestAppContext) {
 7221    init_test(cx, |_| {});
 7222
 7223    let mut cx = EditorTestContext::new(cx).await;
 7224    cx.set_state(
 7225        r#"            «for selection in selections.iter() {
 7226            let mut start = selection.start;
 7227            let mut end = selection.end;
 7228            let is_entire_line = selection.is_empty();
 7229            if is_entire_line {
 7230                start = Point::new(start.row, 0);ˇ»
 7231                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7232            }
 7233        "#,
 7234    );
 7235    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7236    assert_eq!(
 7237        cx.read_from_clipboard()
 7238            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7239        Some(
 7240            "for selection in selections.iter() {
 7241            let mut start = selection.start;
 7242            let mut end = selection.end;
 7243            let is_entire_line = selection.is_empty();
 7244            if is_entire_line {
 7245                start = Point::new(start.row, 0);"
 7246                .to_string()
 7247        ),
 7248        "Regular copying preserves all indentation selected",
 7249    );
 7250    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7251    assert_eq!(
 7252        cx.read_from_clipboard()
 7253            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7254        Some(
 7255            "for selection in selections.iter() {
 7256let mut start = selection.start;
 7257let mut end = selection.end;
 7258let is_entire_line = selection.is_empty();
 7259if is_entire_line {
 7260    start = Point::new(start.row, 0);"
 7261                .to_string()
 7262        ),
 7263        "Copying with stripping should strip all leading whitespaces"
 7264    );
 7265
 7266    cx.set_state(
 7267        r#"       «     for selection in selections.iter() {
 7268            let mut start = selection.start;
 7269            let mut end = selection.end;
 7270            let is_entire_line = selection.is_empty();
 7271            if is_entire_line {
 7272                start = Point::new(start.row, 0);ˇ»
 7273                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7274            }
 7275        "#,
 7276    );
 7277    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7278    assert_eq!(
 7279        cx.read_from_clipboard()
 7280            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7281        Some(
 7282            "     for selection in selections.iter() {
 7283            let mut start = selection.start;
 7284            let mut end = selection.end;
 7285            let is_entire_line = selection.is_empty();
 7286            if is_entire_line {
 7287                start = Point::new(start.row, 0);"
 7288                .to_string()
 7289        ),
 7290        "Regular copying preserves all indentation selected",
 7291    );
 7292    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7293    assert_eq!(
 7294        cx.read_from_clipboard()
 7295            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7296        Some(
 7297            "for selection in selections.iter() {
 7298let mut start = selection.start;
 7299let mut end = selection.end;
 7300let is_entire_line = selection.is_empty();
 7301if is_entire_line {
 7302    start = Point::new(start.row, 0);"
 7303                .to_string()
 7304        ),
 7305        "Copying with stripping should strip all leading whitespaces, even if some of it was selected"
 7306    );
 7307
 7308    cx.set_state(
 7309        r#"       «ˇ     for selection in selections.iter() {
 7310            let mut start = selection.start;
 7311            let mut end = selection.end;
 7312            let is_entire_line = selection.is_empty();
 7313            if is_entire_line {
 7314                start = Point::new(start.row, 0);»
 7315                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7316            }
 7317        "#,
 7318    );
 7319    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7320    assert_eq!(
 7321        cx.read_from_clipboard()
 7322            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7323        Some(
 7324            "     for selection in selections.iter() {
 7325            let mut start = selection.start;
 7326            let mut end = selection.end;
 7327            let is_entire_line = selection.is_empty();
 7328            if is_entire_line {
 7329                start = Point::new(start.row, 0);"
 7330                .to_string()
 7331        ),
 7332        "Regular copying for reverse selection works the same",
 7333    );
 7334    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7335    assert_eq!(
 7336        cx.read_from_clipboard()
 7337            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7338        Some(
 7339            "for selection in selections.iter() {
 7340let mut start = selection.start;
 7341let mut end = selection.end;
 7342let is_entire_line = selection.is_empty();
 7343if is_entire_line {
 7344    start = Point::new(start.row, 0);"
 7345                .to_string()
 7346        ),
 7347        "Copying with stripping for reverse selection works the same"
 7348    );
 7349
 7350    cx.set_state(
 7351        r#"            for selection «in selections.iter() {
 7352            let mut start = selection.start;
 7353            let mut end = selection.end;
 7354            let is_entire_line = selection.is_empty();
 7355            if is_entire_line {
 7356                start = Point::new(start.row, 0);ˇ»
 7357                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7358            }
 7359        "#,
 7360    );
 7361    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7362    assert_eq!(
 7363        cx.read_from_clipboard()
 7364            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7365        Some(
 7366            "in selections.iter() {
 7367            let mut start = selection.start;
 7368            let mut end = selection.end;
 7369            let is_entire_line = selection.is_empty();
 7370            if is_entire_line {
 7371                start = Point::new(start.row, 0);"
 7372                .to_string()
 7373        ),
 7374        "When selecting past the indent, the copying works as usual",
 7375    );
 7376    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7377    assert_eq!(
 7378        cx.read_from_clipboard()
 7379            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7380        Some(
 7381            "in selections.iter() {
 7382            let mut start = selection.start;
 7383            let mut end = selection.end;
 7384            let is_entire_line = selection.is_empty();
 7385            if is_entire_line {
 7386                start = Point::new(start.row, 0);"
 7387                .to_string()
 7388        ),
 7389        "When selecting past the indent, nothing is trimmed"
 7390    );
 7391
 7392    cx.set_state(
 7393        r#"            «for selection in selections.iter() {
 7394            let mut start = selection.start;
 7395
 7396            let mut end = selection.end;
 7397            let is_entire_line = selection.is_empty();
 7398            if is_entire_line {
 7399                start = Point::new(start.row, 0);
 7400ˇ»                end = cmp::min(max_point, Point::new(end.row + 1, 0));
 7401            }
 7402        "#,
 7403    );
 7404    cx.update_editor(|e, window, cx| e.copy_and_trim(&CopyAndTrim, window, cx));
 7405    assert_eq!(
 7406        cx.read_from_clipboard()
 7407            .and_then(|item| item.text().as_deref().map(str::to_string)),
 7408        Some(
 7409            "for selection in selections.iter() {
 7410let mut start = selection.start;
 7411
 7412let mut end = selection.end;
 7413let is_entire_line = selection.is_empty();
 7414if is_entire_line {
 7415    start = Point::new(start.row, 0);
 7416"
 7417            .to_string()
 7418        ),
 7419        "Copying with stripping should ignore empty lines"
 7420    );
 7421}
 7422
 7423#[gpui::test]
 7424async fn test_paste_multiline(cx: &mut TestAppContext) {
 7425    init_test(cx, |_| {});
 7426
 7427    let mut cx = EditorTestContext::new(cx).await;
 7428    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(rust_lang()), cx));
 7429
 7430    // Cut an indented block, without the leading whitespace.
 7431    cx.set_state(indoc! {"
 7432        const a: B = (
 7433            c(),
 7434            «d(
 7435                e,
 7436                f
 7437            )ˇ»
 7438        );
 7439    "});
 7440    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7441    cx.assert_editor_state(indoc! {"
 7442        const a: B = (
 7443            c(),
 7444            ˇ
 7445        );
 7446    "});
 7447
 7448    // Paste it at the same position.
 7449    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7450    cx.assert_editor_state(indoc! {"
 7451        const a: B = (
 7452            c(),
 7453            d(
 7454                e,
 7455                f
 7456 7457        );
 7458    "});
 7459
 7460    // Paste it at a line with a lower indent level.
 7461    cx.set_state(indoc! {"
 7462        ˇ
 7463        const a: B = (
 7464            c(),
 7465        );
 7466    "});
 7467    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7468    cx.assert_editor_state(indoc! {"
 7469        d(
 7470            e,
 7471            f
 7472 7473        const a: B = (
 7474            c(),
 7475        );
 7476    "});
 7477
 7478    // Cut an indented block, with the leading whitespace.
 7479    cx.set_state(indoc! {"
 7480        const a: B = (
 7481            c(),
 7482        «    d(
 7483                e,
 7484                f
 7485            )
 7486        ˇ»);
 7487    "});
 7488    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
 7489    cx.assert_editor_state(indoc! {"
 7490        const a: B = (
 7491            c(),
 7492        ˇ);
 7493    "});
 7494
 7495    // Paste it at the same position.
 7496    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7497    cx.assert_editor_state(indoc! {"
 7498        const a: B = (
 7499            c(),
 7500            d(
 7501                e,
 7502                f
 7503            )
 7504        ˇ);
 7505    "});
 7506
 7507    // Paste it at a line with a higher indent level.
 7508    cx.set_state(indoc! {"
 7509        const a: B = (
 7510            c(),
 7511            d(
 7512                e,
 7513 7514            )
 7515        );
 7516    "});
 7517    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7518    cx.assert_editor_state(indoc! {"
 7519        const a: B = (
 7520            c(),
 7521            d(
 7522                e,
 7523                f    d(
 7524                    e,
 7525                    f
 7526                )
 7527        ˇ
 7528            )
 7529        );
 7530    "});
 7531
 7532    // Copy an indented block, starting mid-line
 7533    cx.set_state(indoc! {"
 7534        const a: B = (
 7535            c(),
 7536            somethin«g(
 7537                e,
 7538                f
 7539            )ˇ»
 7540        );
 7541    "});
 7542    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
 7543
 7544    // Paste it on a line with a lower indent level
 7545    cx.update_editor(|e, window, cx| e.move_to_end(&Default::default(), window, cx));
 7546    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7547    cx.assert_editor_state(indoc! {"
 7548        const a: B = (
 7549            c(),
 7550            something(
 7551                e,
 7552                f
 7553            )
 7554        );
 7555        g(
 7556            e,
 7557            f
 7558"});
 7559}
 7560
 7561#[gpui::test]
 7562async fn test_paste_content_from_other_app(cx: &mut TestAppContext) {
 7563    init_test(cx, |_| {});
 7564
 7565    cx.write_to_clipboard(ClipboardItem::new_string(
 7566        "    d(\n        e\n    );\n".into(),
 7567    ));
 7568
 7569    let mut cx = EditorTestContext::new(cx).await;
 7570    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(rust_lang()), cx));
 7571
 7572    cx.set_state(indoc! {"
 7573        fn a() {
 7574            b();
 7575            if c() {
 7576                ˇ
 7577            }
 7578        }
 7579    "});
 7580
 7581    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7582    cx.assert_editor_state(indoc! {"
 7583        fn a() {
 7584            b();
 7585            if c() {
 7586                d(
 7587                    e
 7588                );
 7589        ˇ
 7590            }
 7591        }
 7592    "});
 7593
 7594    cx.set_state(indoc! {"
 7595        fn a() {
 7596            b();
 7597            ˇ
 7598        }
 7599    "});
 7600
 7601    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
 7602    cx.assert_editor_state(indoc! {"
 7603        fn a() {
 7604            b();
 7605            d(
 7606                e
 7607            );
 7608        ˇ
 7609        }
 7610    "});
 7611}
 7612
 7613#[gpui::test]
 7614fn test_select_all(cx: &mut TestAppContext) {
 7615    init_test(cx, |_| {});
 7616
 7617    let editor = cx.add_window(|window, cx| {
 7618        let buffer = MultiBuffer::build_simple("abc\nde\nfgh", cx);
 7619        build_editor(buffer, window, cx)
 7620    });
 7621    _ = editor.update(cx, |editor, window, cx| {
 7622        editor.select_all(&SelectAll, window, cx);
 7623        assert_eq!(
 7624            display_ranges(editor, cx),
 7625            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 3)]
 7626        );
 7627    });
 7628}
 7629
 7630#[gpui::test]
 7631fn test_select_line(cx: &mut TestAppContext) {
 7632    init_test(cx, |_| {});
 7633
 7634    let editor = cx.add_window(|window, cx| {
 7635        let buffer = MultiBuffer::build_simple(&sample_text(6, 5, 'a'), cx);
 7636        build_editor(buffer, window, cx)
 7637    });
 7638    _ = editor.update(cx, |editor, window, cx| {
 7639        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7640            s.select_display_ranges([
 7641                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7642                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7643                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7644                DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 2),
 7645            ])
 7646        });
 7647        editor.select_line(&SelectLine, window, cx);
 7648        assert_eq!(
 7649            display_ranges(editor, cx),
 7650            vec![
 7651                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(2), 0),
 7652                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 0),
 7653            ]
 7654        );
 7655    });
 7656
 7657    _ = editor.update(cx, |editor, window, cx| {
 7658        editor.select_line(&SelectLine, window, cx);
 7659        assert_eq!(
 7660            display_ranges(editor, cx),
 7661            vec![
 7662                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(3), 0),
 7663                DisplayPoint::new(DisplayRow(4), 0)..DisplayPoint::new(DisplayRow(5), 5),
 7664            ]
 7665        );
 7666    });
 7667
 7668    _ = editor.update(cx, |editor, window, cx| {
 7669        editor.select_line(&SelectLine, window, cx);
 7670        assert_eq!(
 7671            display_ranges(editor, cx),
 7672            vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(5), 5)]
 7673        );
 7674    });
 7675}
 7676
 7677#[gpui::test]
 7678async fn test_split_selection_into_lines(cx: &mut TestAppContext) {
 7679    init_test(cx, |_| {});
 7680    let mut cx = EditorTestContext::new(cx).await;
 7681
 7682    #[track_caller]
 7683    fn test(cx: &mut EditorTestContext, initial_state: &'static str, expected_state: &'static str) {
 7684        cx.set_state(initial_state);
 7685        cx.update_editor(|e, window, cx| {
 7686            e.split_selection_into_lines(&Default::default(), window, cx)
 7687        });
 7688        cx.assert_editor_state(expected_state);
 7689    }
 7690
 7691    // Selection starts and ends at the middle of lines, left-to-right
 7692    test(
 7693        &mut cx,
 7694        "aa\nb«ˇb\ncc\ndd\ne»e\nff",
 7695        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7696    );
 7697    // Same thing, right-to-left
 7698    test(
 7699        &mut cx,
 7700        "aa\nb«b\ncc\ndd\neˇ»e\nff",
 7701        "aa\nbbˇ\nccˇ\nddˇ\neˇe\nff",
 7702    );
 7703
 7704    // Whole buffer, left-to-right, last line *doesn't* end with newline
 7705    test(
 7706        &mut cx,
 7707        "«ˇaa\nbb\ncc\ndd\nee\nff»",
 7708        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7709    );
 7710    // Same thing, right-to-left
 7711    test(
 7712        &mut cx,
 7713        "«aa\nbb\ncc\ndd\nee\nffˇ»",
 7714        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ",
 7715    );
 7716
 7717    // Whole buffer, left-to-right, last line ends with newline
 7718    test(
 7719        &mut cx,
 7720        "«ˇaa\nbb\ncc\ndd\nee\nff\n»",
 7721        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7722    );
 7723    // Same thing, right-to-left
 7724    test(
 7725        &mut cx,
 7726        "«aa\nbb\ncc\ndd\nee\nff\nˇ»",
 7727        "aaˇ\nbbˇ\nccˇ\nddˇ\neeˇ\nffˇ\n",
 7728    );
 7729
 7730    // Starts at the end of a line, ends at the start of another
 7731    test(
 7732        &mut cx,
 7733        "aa\nbb«ˇ\ncc\ndd\nee\n»ff\n",
 7734        "aa\nbbˇ\nccˇ\nddˇ\neeˇ\nff\n",
 7735    );
 7736}
 7737
 7738#[gpui::test]
 7739async fn test_split_selection_into_lines_interacting_with_creases(cx: &mut TestAppContext) {
 7740    init_test(cx, |_| {});
 7741
 7742    let editor = cx.add_window(|window, cx| {
 7743        let buffer = MultiBuffer::build_simple(&sample_text(9, 5, 'a'), cx);
 7744        build_editor(buffer, window, cx)
 7745    });
 7746
 7747    // setup
 7748    _ = editor.update(cx, |editor, window, cx| {
 7749        editor.fold_creases(
 7750            vec![
 7751                Crease::simple(Point::new(0, 2)..Point::new(1, 2), FoldPlaceholder::test()),
 7752                Crease::simple(Point::new(2, 3)..Point::new(4, 1), FoldPlaceholder::test()),
 7753                Crease::simple(Point::new(7, 0)..Point::new(8, 4), FoldPlaceholder::test()),
 7754            ],
 7755            true,
 7756            window,
 7757            cx,
 7758        );
 7759        assert_eq!(
 7760            editor.display_text(cx),
 7761            "aa⋯bbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7762        );
 7763    });
 7764
 7765    _ = editor.update(cx, |editor, window, cx| {
 7766        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7767            s.select_display_ranges([
 7768                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
 7769                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 2),
 7770                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
 7771                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
 7772            ])
 7773        });
 7774        editor.split_selection_into_lines(&Default::default(), window, cx);
 7775        assert_eq!(
 7776            editor.display_text(cx),
 7777            "aaaaa\nbbbbb\nccc⋯eeee\nfffff\nggggg\n⋯i"
 7778        );
 7779    });
 7780    EditorTestContext::for_editor(editor, cx)
 7781        .await
 7782        .assert_editor_state("aˇaˇaaa\nbbbbb\nˇccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiiiˇ");
 7783
 7784    _ = editor.update(cx, |editor, window, cx| {
 7785        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 7786            s.select_display_ranges([
 7787                DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 1)
 7788            ])
 7789        });
 7790        editor.split_selection_into_lines(&Default::default(), window, cx);
 7791        assert_eq!(
 7792            editor.display_text(cx),
 7793            "aaaaa\nbbbbb\nccccc\nddddd\neeeee\nfffff\nggggg\nhhhhh\niiiii"
 7794        );
 7795        assert_eq!(
 7796            display_ranges(editor, cx),
 7797            [
 7798                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5),
 7799                DisplayPoint::new(DisplayRow(1), 5)..DisplayPoint::new(DisplayRow(1), 5),
 7800                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
 7801                DisplayPoint::new(DisplayRow(3), 5)..DisplayPoint::new(DisplayRow(3), 5),
 7802                DisplayPoint::new(DisplayRow(4), 5)..DisplayPoint::new(DisplayRow(4), 5),
 7803                DisplayPoint::new(DisplayRow(5), 5)..DisplayPoint::new(DisplayRow(5), 5),
 7804                DisplayPoint::new(DisplayRow(6), 5)..DisplayPoint::new(DisplayRow(6), 5)
 7805            ]
 7806        );
 7807    });
 7808    EditorTestContext::for_editor(editor, cx)
 7809        .await
 7810        .assert_editor_state(
 7811            "aaaaaˇ\nbbbbbˇ\ncccccˇ\ndddddˇ\neeeeeˇ\nfffffˇ\ngggggˇ\nhhhhh\niiiii",
 7812        );
 7813}
 7814
 7815#[gpui::test]
 7816async fn test_add_selection_above_below(cx: &mut TestAppContext) {
 7817    init_test(cx, |_| {});
 7818
 7819    let mut cx = EditorTestContext::new(cx).await;
 7820
 7821    cx.set_state(indoc!(
 7822        r#"abc
 7823           defˇghi
 7824
 7825           jk
 7826           nlmo
 7827           "#
 7828    ));
 7829
 7830    cx.update_editor(|editor, window, cx| {
 7831        editor.add_selection_above(&Default::default(), window, cx);
 7832    });
 7833
 7834    cx.assert_editor_state(indoc!(
 7835        r#"abcˇ
 7836           defˇghi
 7837
 7838           jk
 7839           nlmo
 7840           "#
 7841    ));
 7842
 7843    cx.update_editor(|editor, window, cx| {
 7844        editor.add_selection_above(&Default::default(), window, cx);
 7845    });
 7846
 7847    cx.assert_editor_state(indoc!(
 7848        r#"abcˇ
 7849            defˇghi
 7850
 7851            jk
 7852            nlmo
 7853            "#
 7854    ));
 7855
 7856    cx.update_editor(|editor, window, cx| {
 7857        editor.add_selection_below(&Default::default(), window, cx);
 7858    });
 7859
 7860    cx.assert_editor_state(indoc!(
 7861        r#"abc
 7862           defˇghi
 7863
 7864           jk
 7865           nlmo
 7866           "#
 7867    ));
 7868
 7869    cx.update_editor(|editor, window, cx| {
 7870        editor.undo_selection(&Default::default(), window, cx);
 7871    });
 7872
 7873    cx.assert_editor_state(indoc!(
 7874        r#"abcˇ
 7875           defˇghi
 7876
 7877           jk
 7878           nlmo
 7879           "#
 7880    ));
 7881
 7882    cx.update_editor(|editor, window, cx| {
 7883        editor.redo_selection(&Default::default(), window, cx);
 7884    });
 7885
 7886    cx.assert_editor_state(indoc!(
 7887        r#"abc
 7888           defˇghi
 7889
 7890           jk
 7891           nlmo
 7892           "#
 7893    ));
 7894
 7895    cx.update_editor(|editor, window, cx| {
 7896        editor.add_selection_below(&Default::default(), window, cx);
 7897    });
 7898
 7899    cx.assert_editor_state(indoc!(
 7900        r#"abc
 7901           defˇghi
 7902           ˇ
 7903           jk
 7904           nlmo
 7905           "#
 7906    ));
 7907
 7908    cx.update_editor(|editor, window, cx| {
 7909        editor.add_selection_below(&Default::default(), window, cx);
 7910    });
 7911
 7912    cx.assert_editor_state(indoc!(
 7913        r#"abc
 7914           defˇghi
 7915           ˇ
 7916           jkˇ
 7917           nlmo
 7918           "#
 7919    ));
 7920
 7921    cx.update_editor(|editor, window, cx| {
 7922        editor.add_selection_below(&Default::default(), window, cx);
 7923    });
 7924
 7925    cx.assert_editor_state(indoc!(
 7926        r#"abc
 7927           defˇghi
 7928           ˇ
 7929           jkˇ
 7930           nlmˇo
 7931           "#
 7932    ));
 7933
 7934    cx.update_editor(|editor, window, cx| {
 7935        editor.add_selection_below(&Default::default(), window, cx);
 7936    });
 7937
 7938    cx.assert_editor_state(indoc!(
 7939        r#"abc
 7940           defˇghi
 7941           ˇ
 7942           jkˇ
 7943           nlmˇo
 7944           ˇ"#
 7945    ));
 7946
 7947    // change selections
 7948    cx.set_state(indoc!(
 7949        r#"abc
 7950           def«ˇg»hi
 7951
 7952           jk
 7953           nlmo
 7954           "#
 7955    ));
 7956
 7957    cx.update_editor(|editor, window, cx| {
 7958        editor.add_selection_below(&Default::default(), window, cx);
 7959    });
 7960
 7961    cx.assert_editor_state(indoc!(
 7962        r#"abc
 7963           def«ˇg»hi
 7964
 7965           jk
 7966           nlm«ˇo»
 7967           "#
 7968    ));
 7969
 7970    cx.update_editor(|editor, window, cx| {
 7971        editor.add_selection_below(&Default::default(), window, cx);
 7972    });
 7973
 7974    cx.assert_editor_state(indoc!(
 7975        r#"abc
 7976           def«ˇg»hi
 7977
 7978           jk
 7979           nlm«ˇo»
 7980           "#
 7981    ));
 7982
 7983    cx.update_editor(|editor, window, cx| {
 7984        editor.add_selection_above(&Default::default(), window, cx);
 7985    });
 7986
 7987    cx.assert_editor_state(indoc!(
 7988        r#"abc
 7989           def«ˇg»hi
 7990
 7991           jk
 7992           nlmo
 7993           "#
 7994    ));
 7995
 7996    cx.update_editor(|editor, window, cx| {
 7997        editor.add_selection_above(&Default::default(), window, cx);
 7998    });
 7999
 8000    cx.assert_editor_state(indoc!(
 8001        r#"abc
 8002           def«ˇg»hi
 8003
 8004           jk
 8005           nlmo
 8006           "#
 8007    ));
 8008
 8009    // Change selections again
 8010    cx.set_state(indoc!(
 8011        r#"a«bc
 8012           defgˇ»hi
 8013
 8014           jk
 8015           nlmo
 8016           "#
 8017    ));
 8018
 8019    cx.update_editor(|editor, window, cx| {
 8020        editor.add_selection_below(&Default::default(), window, cx);
 8021    });
 8022
 8023    cx.assert_editor_state(indoc!(
 8024        r#"a«bcˇ»
 8025           d«efgˇ»hi
 8026
 8027           j«kˇ»
 8028           nlmo
 8029           "#
 8030    ));
 8031
 8032    cx.update_editor(|editor, window, cx| {
 8033        editor.add_selection_below(&Default::default(), window, cx);
 8034    });
 8035    cx.assert_editor_state(indoc!(
 8036        r#"a«bcˇ»
 8037           d«efgˇ»hi
 8038
 8039           j«kˇ»
 8040           n«lmoˇ»
 8041           "#
 8042    ));
 8043    cx.update_editor(|editor, window, cx| {
 8044        editor.add_selection_above(&Default::default(), window, cx);
 8045    });
 8046
 8047    cx.assert_editor_state(indoc!(
 8048        r#"a«bcˇ»
 8049           d«efgˇ»hi
 8050
 8051           j«kˇ»
 8052           nlmo
 8053           "#
 8054    ));
 8055
 8056    // Change selections again
 8057    cx.set_state(indoc!(
 8058        r#"abc
 8059           d«ˇefghi
 8060
 8061           jk
 8062           nlm»o
 8063           "#
 8064    ));
 8065
 8066    cx.update_editor(|editor, window, cx| {
 8067        editor.add_selection_above(&Default::default(), window, cx);
 8068    });
 8069
 8070    cx.assert_editor_state(indoc!(
 8071        r#"a«ˇbc»
 8072           d«ˇef»ghi
 8073
 8074           j«ˇk»
 8075           n«ˇlm»o
 8076           "#
 8077    ));
 8078
 8079    cx.update_editor(|editor, window, cx| {
 8080        editor.add_selection_below(&Default::default(), window, cx);
 8081    });
 8082
 8083    cx.assert_editor_state(indoc!(
 8084        r#"abc
 8085           d«ˇef»ghi
 8086
 8087           j«ˇk»
 8088           n«ˇlm»o
 8089           "#
 8090    ));
 8091}
 8092
 8093#[gpui::test]
 8094async fn test_add_selection_above_below_multi_cursor(cx: &mut TestAppContext) {
 8095    init_test(cx, |_| {});
 8096    let mut cx = EditorTestContext::new(cx).await;
 8097
 8098    cx.set_state(indoc!(
 8099        r#"line onˇe
 8100           liˇne two
 8101           line three
 8102           line four"#
 8103    ));
 8104
 8105    cx.update_editor(|editor, window, cx| {
 8106        editor.add_selection_below(&Default::default(), window, cx);
 8107    });
 8108
 8109    // test multiple cursors expand in the same direction
 8110    cx.assert_editor_state(indoc!(
 8111        r#"line onˇe
 8112           liˇne twˇo
 8113           liˇne three
 8114           line four"#
 8115    ));
 8116
 8117    cx.update_editor(|editor, window, cx| {
 8118        editor.add_selection_below(&Default::default(), window, cx);
 8119    });
 8120
 8121    cx.update_editor(|editor, window, cx| {
 8122        editor.add_selection_below(&Default::default(), window, cx);
 8123    });
 8124
 8125    // test multiple cursors expand below overflow
 8126    cx.assert_editor_state(indoc!(
 8127        r#"line onˇe
 8128           liˇne twˇo
 8129           liˇne thˇree
 8130           liˇne foˇur"#
 8131    ));
 8132
 8133    cx.update_editor(|editor, window, cx| {
 8134        editor.add_selection_above(&Default::default(), window, cx);
 8135    });
 8136
 8137    // test multiple cursors retrieves back correctly
 8138    cx.assert_editor_state(indoc!(
 8139        r#"line onˇe
 8140           liˇne twˇo
 8141           liˇne thˇree
 8142           line four"#
 8143    ));
 8144
 8145    cx.update_editor(|editor, window, cx| {
 8146        editor.add_selection_above(&Default::default(), window, cx);
 8147    });
 8148
 8149    cx.update_editor(|editor, window, cx| {
 8150        editor.add_selection_above(&Default::default(), window, cx);
 8151    });
 8152
 8153    // test multiple cursor groups maintain independent direction - first expands up, second shrinks above
 8154    cx.assert_editor_state(indoc!(
 8155        r#"liˇne onˇe
 8156           liˇne two
 8157           line three
 8158           line four"#
 8159    ));
 8160
 8161    cx.update_editor(|editor, window, cx| {
 8162        editor.undo_selection(&Default::default(), window, cx);
 8163    });
 8164
 8165    // test undo
 8166    cx.assert_editor_state(indoc!(
 8167        r#"line onˇe
 8168           liˇne twˇo
 8169           line three
 8170           line four"#
 8171    ));
 8172
 8173    cx.update_editor(|editor, window, cx| {
 8174        editor.redo_selection(&Default::default(), window, cx);
 8175    });
 8176
 8177    // test redo
 8178    cx.assert_editor_state(indoc!(
 8179        r#"liˇne onˇe
 8180           liˇne two
 8181           line three
 8182           line four"#
 8183    ));
 8184
 8185    cx.set_state(indoc!(
 8186        r#"abcd
 8187           ef«ghˇ»
 8188           ijkl
 8189           «mˇ»nop"#
 8190    ));
 8191
 8192    cx.update_editor(|editor, window, cx| {
 8193        editor.add_selection_above(&Default::default(), window, cx);
 8194    });
 8195
 8196    // test multiple selections expand in the same direction
 8197    cx.assert_editor_state(indoc!(
 8198        r#"ab«cdˇ»
 8199           ef«ghˇ»
 8200           «iˇ»jkl
 8201           «mˇ»nop"#
 8202    ));
 8203
 8204    cx.update_editor(|editor, window, cx| {
 8205        editor.add_selection_above(&Default::default(), window, cx);
 8206    });
 8207
 8208    // test multiple selection upward overflow
 8209    cx.assert_editor_state(indoc!(
 8210        r#"ab«cdˇ»
 8211           «eˇ»f«ghˇ»
 8212           «iˇ»jkl
 8213           «mˇ»nop"#
 8214    ));
 8215
 8216    cx.update_editor(|editor, window, cx| {
 8217        editor.add_selection_below(&Default::default(), window, cx);
 8218    });
 8219
 8220    // test multiple selection retrieves back correctly
 8221    cx.assert_editor_state(indoc!(
 8222        r#"abcd
 8223           ef«ghˇ»
 8224           «iˇ»jkl
 8225           «mˇ»nop"#
 8226    ));
 8227
 8228    cx.update_editor(|editor, window, cx| {
 8229        editor.add_selection_below(&Default::default(), window, cx);
 8230    });
 8231
 8232    // test multiple cursor groups maintain independent direction - first shrinks down, second expands below
 8233    cx.assert_editor_state(indoc!(
 8234        r#"abcd
 8235           ef«ghˇ»
 8236           ij«klˇ»
 8237           «mˇ»nop"#
 8238    ));
 8239
 8240    cx.update_editor(|editor, window, cx| {
 8241        editor.undo_selection(&Default::default(), window, cx);
 8242    });
 8243
 8244    // test undo
 8245    cx.assert_editor_state(indoc!(
 8246        r#"abcd
 8247           ef«ghˇ»
 8248           «iˇ»jkl
 8249           «mˇ»nop"#
 8250    ));
 8251
 8252    cx.update_editor(|editor, window, cx| {
 8253        editor.redo_selection(&Default::default(), window, cx);
 8254    });
 8255
 8256    // test redo
 8257    cx.assert_editor_state(indoc!(
 8258        r#"abcd
 8259           ef«ghˇ»
 8260           ij«klˇ»
 8261           «mˇ»nop"#
 8262    ));
 8263}
 8264
 8265#[gpui::test]
 8266async fn test_add_selection_above_below_multi_cursor_existing_state(cx: &mut TestAppContext) {
 8267    init_test(cx, |_| {});
 8268    let mut cx = EditorTestContext::new(cx).await;
 8269
 8270    cx.set_state(indoc!(
 8271        r#"line onˇe
 8272           liˇne two
 8273           line three
 8274           line four"#
 8275    ));
 8276
 8277    cx.update_editor(|editor, window, cx| {
 8278        editor.add_selection_below(&Default::default(), window, cx);
 8279        editor.add_selection_below(&Default::default(), window, cx);
 8280        editor.add_selection_below(&Default::default(), window, cx);
 8281    });
 8282
 8283    // initial state with two multi cursor groups
 8284    cx.assert_editor_state(indoc!(
 8285        r#"line onˇe
 8286           liˇne twˇo
 8287           liˇne thˇree
 8288           liˇne foˇur"#
 8289    ));
 8290
 8291    // add single cursor in middle - simulate opt click
 8292    cx.update_editor(|editor, window, cx| {
 8293        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 4);
 8294        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8295        editor.end_selection(window, cx);
 8296    });
 8297
 8298    cx.assert_editor_state(indoc!(
 8299        r#"line onˇe
 8300           liˇne twˇo
 8301           liˇneˇ thˇree
 8302           liˇne foˇur"#
 8303    ));
 8304
 8305    cx.update_editor(|editor, window, cx| {
 8306        editor.add_selection_above(&Default::default(), window, cx);
 8307    });
 8308
 8309    // test new added selection expands above and existing selection shrinks
 8310    cx.assert_editor_state(indoc!(
 8311        r#"line onˇe
 8312           liˇneˇ twˇo
 8313           liˇneˇ thˇree
 8314           line four"#
 8315    ));
 8316
 8317    cx.update_editor(|editor, window, cx| {
 8318        editor.add_selection_above(&Default::default(), window, cx);
 8319    });
 8320
 8321    // test new added selection expands above and existing selection shrinks
 8322    cx.assert_editor_state(indoc!(
 8323        r#"lineˇ onˇe
 8324           liˇneˇ twˇo
 8325           lineˇ three
 8326           line four"#
 8327    ));
 8328
 8329    // intial state with two selection groups
 8330    cx.set_state(indoc!(
 8331        r#"abcd
 8332           ef«ghˇ»
 8333           ijkl
 8334           «mˇ»nop"#
 8335    ));
 8336
 8337    cx.update_editor(|editor, window, cx| {
 8338        editor.add_selection_above(&Default::default(), window, cx);
 8339        editor.add_selection_above(&Default::default(), window, cx);
 8340    });
 8341
 8342    cx.assert_editor_state(indoc!(
 8343        r#"ab«cdˇ»
 8344           «eˇ»f«ghˇ»
 8345           «iˇ»jkl
 8346           «mˇ»nop"#
 8347    ));
 8348
 8349    // add single selection in middle - simulate opt drag
 8350    cx.update_editor(|editor, window, cx| {
 8351        let new_cursor_point = DisplayPoint::new(DisplayRow(2), 3);
 8352        editor.begin_selection(new_cursor_point, true, 1, window, cx);
 8353        editor.update_selection(
 8354            DisplayPoint::new(DisplayRow(2), 4),
 8355            0,
 8356            gpui::Point::<f32>::default(),
 8357            window,
 8358            cx,
 8359        );
 8360        editor.end_selection(window, cx);
 8361    });
 8362
 8363    cx.assert_editor_state(indoc!(
 8364        r#"ab«cdˇ»
 8365           «eˇ»f«ghˇ»
 8366           «iˇ»jk«lˇ»
 8367           «mˇ»nop"#
 8368    ));
 8369
 8370    cx.update_editor(|editor, window, cx| {
 8371        editor.add_selection_below(&Default::default(), window, cx);
 8372    });
 8373
 8374    // test new added selection expands below, others shrinks from above
 8375    cx.assert_editor_state(indoc!(
 8376        r#"abcd
 8377           ef«ghˇ»
 8378           «iˇ»jk«lˇ»
 8379           «mˇ»no«pˇ»"#
 8380    ));
 8381}
 8382
 8383#[gpui::test]
 8384async fn test_select_next(cx: &mut TestAppContext) {
 8385    init_test(cx, |_| {});
 8386    let mut cx = EditorTestContext::new(cx).await;
 8387
 8388    // Enable case sensitive search.
 8389    update_test_editor_settings(&mut cx, |settings| {
 8390        let mut search_settings = SearchSettingsContent::default();
 8391        search_settings.case_sensitive = Some(true);
 8392        settings.search = Some(search_settings);
 8393    });
 8394
 8395    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8396
 8397    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8398        .unwrap();
 8399    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8400
 8401    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8402        .unwrap();
 8403    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8404
 8405    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8406    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8407
 8408    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8409    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\nabc");
 8410
 8411    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8412        .unwrap();
 8413    cx.assert_editor_state("abc\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8414
 8415    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8416        .unwrap();
 8417    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8418
 8419    // Test selection direction should be preserved
 8420    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8421
 8422    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8423        .unwrap();
 8424    cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
 8425
 8426    // Test case sensitivity
 8427    cx.set_state("«ˇfoo»\nFOO\nFoo\nfoo");
 8428    cx.update_editor(|e, window, cx| {
 8429        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8430    });
 8431    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8432
 8433    // Disable case sensitive search.
 8434    update_test_editor_settings(&mut cx, |settings| {
 8435        let mut search_settings = SearchSettingsContent::default();
 8436        search_settings.case_sensitive = Some(false);
 8437        settings.search = Some(search_settings);
 8438    });
 8439
 8440    cx.set_state("«ˇfoo»\nFOO\nFoo");
 8441    cx.update_editor(|e, window, cx| {
 8442        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8443        e.select_next(&SelectNext::default(), window, cx).unwrap();
 8444    });
 8445    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8446}
 8447
 8448#[gpui::test]
 8449async fn test_select_all_matches(cx: &mut TestAppContext) {
 8450    init_test(cx, |_| {});
 8451    let mut cx = EditorTestContext::new(cx).await;
 8452
 8453    // Enable case sensitive search.
 8454    update_test_editor_settings(&mut cx, |settings| {
 8455        let mut search_settings = SearchSettingsContent::default();
 8456        search_settings.case_sensitive = Some(true);
 8457        settings.search = Some(search_settings);
 8458    });
 8459
 8460    // Test caret-only selections
 8461    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8462    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8463        .unwrap();
 8464    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8465
 8466    // Test left-to-right selections
 8467    cx.set_state("abc\n«abcˇ»\nabc");
 8468    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8469        .unwrap();
 8470    cx.assert_editor_state("«abcˇ»\n«abcˇ»\n«abcˇ»");
 8471
 8472    // Test right-to-left selections
 8473    cx.set_state("abc\n«ˇabc»\nabc");
 8474    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8475        .unwrap();
 8476    cx.assert_editor_state("«ˇabc»\n«ˇabc»\n«ˇabc»");
 8477
 8478    // Test selecting whitespace with caret selection
 8479    cx.set_state("abc\nˇ   abc\nabc");
 8480    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8481        .unwrap();
 8482    cx.assert_editor_state("abc\n«   ˇ»abc\nabc");
 8483
 8484    // Test selecting whitespace with left-to-right selection
 8485    cx.set_state("abc\n«ˇ  »abc\nabc");
 8486    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8487        .unwrap();
 8488    cx.assert_editor_state("abc\n«ˇ  »abc\nabc");
 8489
 8490    // Test no matches with right-to-left selection
 8491    cx.set_state("abc\n«  ˇ»abc\nabc");
 8492    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8493        .unwrap();
 8494    cx.assert_editor_state("abc\n«  ˇ»abc\nabc");
 8495
 8496    // Test with a single word and clip_at_line_ends=true (#29823)
 8497    cx.set_state("aˇbc");
 8498    cx.update_editor(|e, window, cx| {
 8499        e.set_clip_at_line_ends(true, cx);
 8500        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8501        e.set_clip_at_line_ends(false, cx);
 8502    });
 8503    cx.assert_editor_state("«abcˇ»");
 8504
 8505    // Test case sensitivity
 8506    cx.set_state("fˇoo\nFOO\nFoo");
 8507    cx.update_editor(|e, window, cx| {
 8508        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8509    });
 8510    cx.assert_editor_state("«fooˇ»\nFOO\nFoo");
 8511
 8512    // Disable case sensitive search.
 8513    update_test_editor_settings(&mut cx, |settings| {
 8514        let mut search_settings = SearchSettingsContent::default();
 8515        search_settings.case_sensitive = Some(false);
 8516        settings.search = Some(search_settings);
 8517    });
 8518
 8519    cx.set_state("fˇoo\nFOO\nFoo");
 8520    cx.update_editor(|e, window, cx| {
 8521        e.select_all_matches(&SelectAllMatches, window, cx).unwrap();
 8522    });
 8523    cx.assert_editor_state("«fooˇ»\n«FOOˇ»\n«Fooˇ»");
 8524}
 8525
 8526#[gpui::test]
 8527async fn test_select_all_matches_does_not_scroll(cx: &mut TestAppContext) {
 8528    init_test(cx, |_| {});
 8529
 8530    let mut cx = EditorTestContext::new(cx).await;
 8531
 8532    let large_body_1 = "\nd".repeat(200);
 8533    let large_body_2 = "\ne".repeat(200);
 8534
 8535    cx.set_state(&format!(
 8536        "abc\nabc{large_body_1} «ˇa»bc{large_body_2}\nefabc\nabc"
 8537    ));
 8538    let initial_scroll_position = cx.update_editor(|editor, _, cx| {
 8539        let scroll_position = editor.scroll_position(cx);
 8540        assert!(scroll_position.y > 0.0, "Initial selection is between two large bodies and should have the editor scrolled to it");
 8541        scroll_position
 8542    });
 8543
 8544    cx.update_editor(|e, window, cx| e.select_all_matches(&SelectAllMatches, window, cx))
 8545        .unwrap();
 8546    cx.assert_editor_state(&format!(
 8547        "«ˇa»bc\n«ˇa»bc{large_body_1} «ˇa»bc{large_body_2}\nef«ˇa»bc\n«ˇa»bc"
 8548    ));
 8549    let scroll_position_after_selection =
 8550        cx.update_editor(|editor, _, cx| editor.scroll_position(cx));
 8551    assert_eq!(
 8552        initial_scroll_position, scroll_position_after_selection,
 8553        "Scroll position should not change after selecting all matches"
 8554    );
 8555}
 8556
 8557#[gpui::test]
 8558async fn test_undo_format_scrolls_to_last_edit_pos(cx: &mut TestAppContext) {
 8559    init_test(cx, |_| {});
 8560
 8561    let mut cx = EditorLspTestContext::new_rust(
 8562        lsp::ServerCapabilities {
 8563            document_formatting_provider: Some(lsp::OneOf::Left(true)),
 8564            ..Default::default()
 8565        },
 8566        cx,
 8567    )
 8568    .await;
 8569
 8570    cx.set_state(indoc! {"
 8571        line 1
 8572        line 2
 8573        linˇe 3
 8574        line 4
 8575        line 5
 8576    "});
 8577
 8578    // Make an edit
 8579    cx.update_editor(|editor, window, cx| {
 8580        editor.handle_input("X", window, cx);
 8581    });
 8582
 8583    // Move cursor to a different position
 8584    cx.update_editor(|editor, window, cx| {
 8585        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8586            s.select_ranges([Point::new(4, 2)..Point::new(4, 2)]);
 8587        });
 8588    });
 8589
 8590    cx.assert_editor_state(indoc! {"
 8591        line 1
 8592        line 2
 8593        linXe 3
 8594        line 4
 8595        liˇne 5
 8596    "});
 8597
 8598    cx.lsp
 8599        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| async move {
 8600            Ok(Some(vec![lsp::TextEdit::new(
 8601                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
 8602                "PREFIX ".to_string(),
 8603            )]))
 8604        });
 8605
 8606    cx.update_editor(|editor, window, cx| editor.format(&Default::default(), window, cx))
 8607        .unwrap()
 8608        .await
 8609        .unwrap();
 8610
 8611    cx.assert_editor_state(indoc! {"
 8612        PREFIX line 1
 8613        line 2
 8614        linXe 3
 8615        line 4
 8616        liˇne 5
 8617    "});
 8618
 8619    // Undo formatting
 8620    cx.update_editor(|editor, window, cx| {
 8621        editor.undo(&Default::default(), window, cx);
 8622    });
 8623
 8624    // Verify cursor moved back to position after edit
 8625    cx.assert_editor_state(indoc! {"
 8626        line 1
 8627        line 2
 8628        linXˇe 3
 8629        line 4
 8630        line 5
 8631    "});
 8632}
 8633
 8634#[gpui::test]
 8635async fn test_undo_edit_prediction_scrolls_to_edit_pos(cx: &mut TestAppContext) {
 8636    init_test(cx, |_| {});
 8637
 8638    let mut cx = EditorTestContext::new(cx).await;
 8639
 8640    let provider = cx.new(|_| FakeEditPredictionProvider::default());
 8641    cx.update_editor(|editor, window, cx| {
 8642        editor.set_edit_prediction_provider(Some(provider.clone()), window, cx);
 8643    });
 8644
 8645    cx.set_state(indoc! {"
 8646        line 1
 8647        line 2
 8648        linˇe 3
 8649        line 4
 8650        line 5
 8651        line 6
 8652        line 7
 8653        line 8
 8654        line 9
 8655        line 10
 8656    "});
 8657
 8658    let snapshot = cx.buffer_snapshot();
 8659    let edit_position = snapshot.anchor_after(Point::new(2, 4));
 8660
 8661    cx.update(|_, cx| {
 8662        provider.update(cx, |provider, _| {
 8663            provider.set_edit_prediction(Some(edit_prediction::EditPrediction::Local {
 8664                id: None,
 8665                edits: vec![(edit_position..edit_position, "X".into())],
 8666                edit_preview: None,
 8667            }))
 8668        })
 8669    });
 8670
 8671    cx.update_editor(|editor, window, cx| editor.update_visible_edit_prediction(window, cx));
 8672    cx.update_editor(|editor, window, cx| {
 8673        editor.accept_edit_prediction(&crate::AcceptEditPrediction, window, cx)
 8674    });
 8675
 8676    cx.assert_editor_state(indoc! {"
 8677        line 1
 8678        line 2
 8679        lineXˇ 3
 8680        line 4
 8681        line 5
 8682        line 6
 8683        line 7
 8684        line 8
 8685        line 9
 8686        line 10
 8687    "});
 8688
 8689    cx.update_editor(|editor, window, cx| {
 8690        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8691            s.select_ranges([Point::new(9, 2)..Point::new(9, 2)]);
 8692        });
 8693    });
 8694
 8695    cx.assert_editor_state(indoc! {"
 8696        line 1
 8697        line 2
 8698        lineX 3
 8699        line 4
 8700        line 5
 8701        line 6
 8702        line 7
 8703        line 8
 8704        line 9
 8705        liˇne 10
 8706    "});
 8707
 8708    cx.update_editor(|editor, window, cx| {
 8709        editor.undo(&Default::default(), window, cx);
 8710    });
 8711
 8712    cx.assert_editor_state(indoc! {"
 8713        line 1
 8714        line 2
 8715        lineˇ 3
 8716        line 4
 8717        line 5
 8718        line 6
 8719        line 7
 8720        line 8
 8721        line 9
 8722        line 10
 8723    "});
 8724}
 8725
 8726#[gpui::test]
 8727async fn test_select_next_with_multiple_carets(cx: &mut TestAppContext) {
 8728    init_test(cx, |_| {});
 8729
 8730    let mut cx = EditorTestContext::new(cx).await;
 8731    cx.set_state(
 8732        r#"let foo = 2;
 8733lˇet foo = 2;
 8734let fooˇ = 2;
 8735let foo = 2;
 8736let foo = ˇ2;"#,
 8737    );
 8738
 8739    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8740        .unwrap();
 8741    cx.assert_editor_state(
 8742        r#"let foo = 2;
 8743«letˇ» foo = 2;
 8744let «fooˇ» = 2;
 8745let foo = 2;
 8746let foo = «2ˇ»;"#,
 8747    );
 8748
 8749    // noop for multiple selections with different contents
 8750    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8751        .unwrap();
 8752    cx.assert_editor_state(
 8753        r#"let foo = 2;
 8754«letˇ» foo = 2;
 8755let «fooˇ» = 2;
 8756let foo = 2;
 8757let foo = «2ˇ»;"#,
 8758    );
 8759
 8760    // Test last selection direction should be preserved
 8761    cx.set_state(
 8762        r#"let foo = 2;
 8763let foo = 2;
 8764let «fooˇ» = 2;
 8765let «ˇfoo» = 2;
 8766let foo = 2;"#,
 8767    );
 8768
 8769    cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
 8770        .unwrap();
 8771    cx.assert_editor_state(
 8772        r#"let foo = 2;
 8773let foo = 2;
 8774let «fooˇ» = 2;
 8775let «ˇfoo» = 2;
 8776let «ˇfoo» = 2;"#,
 8777    );
 8778}
 8779
 8780#[gpui::test]
 8781async fn test_select_previous_multibuffer(cx: &mut TestAppContext) {
 8782    init_test(cx, |_| {});
 8783
 8784    let mut cx =
 8785        EditorTestContext::new_multibuffer(cx, ["aaa\n«bbb\nccc\n»ddd", "aaa\n«bbb\nccc\n»ddd"]);
 8786
 8787    cx.assert_editor_state(indoc! {"
 8788        ˇbbb
 8789        ccc
 8790
 8791        bbb
 8792        ccc
 8793        "});
 8794    cx.dispatch_action(SelectPrevious::default());
 8795    cx.assert_editor_state(indoc! {"
 8796                «bbbˇ»
 8797                ccc
 8798
 8799                bbb
 8800                ccc
 8801                "});
 8802    cx.dispatch_action(SelectPrevious::default());
 8803    cx.assert_editor_state(indoc! {"
 8804                «bbbˇ»
 8805                ccc
 8806
 8807                «bbbˇ»
 8808                ccc
 8809                "});
 8810}
 8811
 8812#[gpui::test]
 8813async fn test_select_previous_with_single_caret(cx: &mut TestAppContext) {
 8814    init_test(cx, |_| {});
 8815
 8816    let mut cx = EditorTestContext::new(cx).await;
 8817    cx.set_state("abc\nˇabc abc\ndefabc\nabc");
 8818
 8819    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8820        .unwrap();
 8821    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8822
 8823    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8824        .unwrap();
 8825    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8826
 8827    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8828    cx.assert_editor_state("abc\n«abcˇ» abc\ndefabc\nabc");
 8829
 8830    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8831    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\nabc");
 8832
 8833    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8834        .unwrap();
 8835    cx.assert_editor_state("«abcˇ»\n«abcˇ» abc\ndefabc\n«abcˇ»");
 8836
 8837    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8838        .unwrap();
 8839    cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
 8840}
 8841
 8842#[gpui::test]
 8843async fn test_select_previous_empty_buffer(cx: &mut TestAppContext) {
 8844    init_test(cx, |_| {});
 8845
 8846    let mut cx = EditorTestContext::new(cx).await;
 8847    cx.set_state("");
 8848
 8849    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8850        .unwrap();
 8851    cx.assert_editor_state("«aˇ»");
 8852    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8853        .unwrap();
 8854    cx.assert_editor_state("«aˇ»");
 8855}
 8856
 8857#[gpui::test]
 8858async fn test_select_previous_with_multiple_carets(cx: &mut TestAppContext) {
 8859    init_test(cx, |_| {});
 8860
 8861    let mut cx = EditorTestContext::new(cx).await;
 8862    cx.set_state(
 8863        r#"let foo = 2;
 8864lˇet foo = 2;
 8865let fooˇ = 2;
 8866let foo = 2;
 8867let foo = ˇ2;"#,
 8868    );
 8869
 8870    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8871        .unwrap();
 8872    cx.assert_editor_state(
 8873        r#"let foo = 2;
 8874«letˇ» foo = 2;
 8875let «fooˇ» = 2;
 8876let foo = 2;
 8877let foo = «2ˇ»;"#,
 8878    );
 8879
 8880    // noop for multiple selections with different contents
 8881    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8882        .unwrap();
 8883    cx.assert_editor_state(
 8884        r#"let foo = 2;
 8885«letˇ» foo = 2;
 8886let «fooˇ» = 2;
 8887let foo = 2;
 8888let foo = «2ˇ»;"#,
 8889    );
 8890}
 8891
 8892#[gpui::test]
 8893async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
 8894    init_test(cx, |_| {});
 8895    let mut cx = EditorTestContext::new(cx).await;
 8896
 8897    // Enable case sensitive search.
 8898    update_test_editor_settings(&mut cx, |settings| {
 8899        let mut search_settings = SearchSettingsContent::default();
 8900        search_settings.case_sensitive = Some(true);
 8901        settings.search = Some(search_settings);
 8902    });
 8903
 8904    cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
 8905
 8906    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8907        .unwrap();
 8908    // selection direction is preserved
 8909    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8910
 8911    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8912        .unwrap();
 8913    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8914
 8915    cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
 8916    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
 8917
 8918    cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
 8919    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
 8920
 8921    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8922        .unwrap();
 8923    cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
 8924
 8925    cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
 8926        .unwrap();
 8927    cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
 8928
 8929    // Test case sensitivity
 8930    cx.set_state("foo\nFOO\nFoo\n«ˇfoo»");
 8931    cx.update_editor(|e, window, cx| {
 8932        e.select_previous(&SelectPrevious::default(), window, cx)
 8933            .unwrap();
 8934        e.select_previous(&SelectPrevious::default(), window, cx)
 8935            .unwrap();
 8936    });
 8937    cx.assert_editor_state("«ˇfoo»\nFOO\nFoo\n«ˇfoo»");
 8938
 8939    // Disable case sensitive search.
 8940    update_test_editor_settings(&mut cx, |settings| {
 8941        let mut search_settings = SearchSettingsContent::default();
 8942        search_settings.case_sensitive = Some(false);
 8943        settings.search = Some(search_settings);
 8944    });
 8945
 8946    cx.set_state("foo\nFOO\n«ˇFoo»");
 8947    cx.update_editor(|e, window, cx| {
 8948        e.select_previous(&SelectPrevious::default(), window, cx)
 8949            .unwrap();
 8950        e.select_previous(&SelectPrevious::default(), window, cx)
 8951            .unwrap();
 8952    });
 8953    cx.assert_editor_state("«ˇfoo»\n«ˇFOO»\n«ˇFoo»");
 8954}
 8955
 8956#[gpui::test]
 8957async fn test_select_larger_smaller_syntax_node(cx: &mut TestAppContext) {
 8958    init_test(cx, |_| {});
 8959
 8960    let language = Arc::new(Language::new(
 8961        LanguageConfig::default(),
 8962        Some(tree_sitter_rust::LANGUAGE.into()),
 8963    ));
 8964
 8965    let text = r#"
 8966        use mod1::mod2::{mod3, mod4};
 8967
 8968        fn fn_1(param1: bool, param2: &str) {
 8969            let var1 = "text";
 8970        }
 8971    "#
 8972    .unindent();
 8973
 8974    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
 8975    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 8976    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 8977
 8978    editor
 8979        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 8980        .await;
 8981
 8982    editor.update_in(cx, |editor, window, cx| {
 8983        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 8984            s.select_display_ranges([
 8985                DisplayPoint::new(DisplayRow(0), 25)..DisplayPoint::new(DisplayRow(0), 25),
 8986                DisplayPoint::new(DisplayRow(2), 24)..DisplayPoint::new(DisplayRow(2), 12),
 8987                DisplayPoint::new(DisplayRow(3), 18)..DisplayPoint::new(DisplayRow(3), 18),
 8988            ]);
 8989        });
 8990        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 8991    });
 8992    editor.update(cx, |editor, cx| {
 8993        assert_text_with_selections(
 8994            editor,
 8995            indoc! {r#"
 8996                use mod1::mod2::{mod3, «mod4ˇ»};
 8997
 8998                fn fn_1«ˇ(param1: bool, param2: &str)» {
 8999                    let var1 = "«ˇtext»";
 9000                }
 9001            "#},
 9002            cx,
 9003        );
 9004    });
 9005
 9006    editor.update_in(cx, |editor, window, cx| {
 9007        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9008    });
 9009    editor.update(cx, |editor, cx| {
 9010        assert_text_with_selections(
 9011            editor,
 9012            indoc! {r#"
 9013                use mod1::mod2::«{mod3, mod4}ˇ»;
 9014
 9015                «ˇfn fn_1(param1: bool, param2: &str) {
 9016                    let var1 = "text";
 9017 9018            "#},
 9019            cx,
 9020        );
 9021    });
 9022
 9023    editor.update_in(cx, |editor, window, cx| {
 9024        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9025    });
 9026    assert_eq!(
 9027        editor.update(cx, |editor, cx| editor
 9028            .selections
 9029            .display_ranges(&editor.display_snapshot(cx))),
 9030        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9031    );
 9032
 9033    // Trying to expand the selected syntax node one more time has no effect.
 9034    editor.update_in(cx, |editor, window, cx| {
 9035        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9036    });
 9037    assert_eq!(
 9038        editor.update(cx, |editor, cx| editor
 9039            .selections
 9040            .display_ranges(&editor.display_snapshot(cx))),
 9041        &[DisplayPoint::new(DisplayRow(5), 0)..DisplayPoint::new(DisplayRow(0), 0)]
 9042    );
 9043
 9044    editor.update_in(cx, |editor, window, cx| {
 9045        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9046    });
 9047    editor.update(cx, |editor, cx| {
 9048        assert_text_with_selections(
 9049            editor,
 9050            indoc! {r#"
 9051                use mod1::mod2::«{mod3, mod4}ˇ»;
 9052
 9053                «ˇfn fn_1(param1: bool, param2: &str) {
 9054                    let var1 = "text";
 9055 9056            "#},
 9057            cx,
 9058        );
 9059    });
 9060
 9061    editor.update_in(cx, |editor, window, cx| {
 9062        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9063    });
 9064    editor.update(cx, |editor, cx| {
 9065        assert_text_with_selections(
 9066            editor,
 9067            indoc! {r#"
 9068                use mod1::mod2::{mod3, «mod4ˇ»};
 9069
 9070                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9071                    let var1 = "«ˇtext»";
 9072                }
 9073            "#},
 9074            cx,
 9075        );
 9076    });
 9077
 9078    editor.update_in(cx, |editor, window, cx| {
 9079        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9080    });
 9081    editor.update(cx, |editor, cx| {
 9082        assert_text_with_selections(
 9083            editor,
 9084            indoc! {r#"
 9085                use mod1::mod2::{mod3, moˇd4};
 9086
 9087                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9088                    let var1 = "teˇxt";
 9089                }
 9090            "#},
 9091            cx,
 9092        );
 9093    });
 9094
 9095    // Trying to shrink the selected syntax node one more time has no effect.
 9096    editor.update_in(cx, |editor, window, cx| {
 9097        editor.select_smaller_syntax_node(&SelectSmallerSyntaxNode, window, cx);
 9098    });
 9099    editor.update_in(cx, |editor, _, cx| {
 9100        assert_text_with_selections(
 9101            editor,
 9102            indoc! {r#"
 9103                use mod1::mod2::{mod3, moˇd4};
 9104
 9105                fn fn_1(para«ˇm1: bool, pa»ram2: &str) {
 9106                    let var1 = "teˇxt";
 9107                }
 9108            "#},
 9109            cx,
 9110        );
 9111    });
 9112
 9113    // Ensure that we keep expanding the selection if the larger selection starts or ends within
 9114    // a fold.
 9115    editor.update_in(cx, |editor, window, cx| {
 9116        editor.fold_creases(
 9117            vec![
 9118                Crease::simple(
 9119                    Point::new(0, 21)..Point::new(0, 24),
 9120                    FoldPlaceholder::test(),
 9121                ),
 9122                Crease::simple(
 9123                    Point::new(3, 20)..Point::new(3, 22),
 9124                    FoldPlaceholder::test(),
 9125                ),
 9126            ],
 9127            true,
 9128            window,
 9129            cx,
 9130        );
 9131        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9132    });
 9133    editor.update(cx, |editor, cx| {
 9134        assert_text_with_selections(
 9135            editor,
 9136            indoc! {r#"
 9137                use mod1::mod2::«{mod3, mod4}ˇ»;
 9138
 9139                fn fn_1«ˇ(param1: bool, param2: &str)» {
 9140                    let var1 = "«ˇtext»";
 9141                }
 9142            "#},
 9143            cx,
 9144        );
 9145    });
 9146}
 9147
 9148#[gpui::test]
 9149async fn test_select_larger_syntax_node_for_cursor_at_end(cx: &mut TestAppContext) {
 9150    init_test(cx, |_| {});
 9151
 9152    let language = Arc::new(Language::new(
 9153        LanguageConfig::default(),
 9154        Some(tree_sitter_rust::LANGUAGE.into()),
 9155    ));
 9156
 9157    let text = "let a = 2;";
 9158
 9159    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
 9160    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9161    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9162
 9163    editor
 9164        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9165        .await;
 9166
 9167    // Test case 1: Cursor at end of word
 9168    editor.update_in(cx, |editor, window, cx| {
 9169        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9170            s.select_display_ranges([
 9171                DisplayPoint::new(DisplayRow(0), 5)..DisplayPoint::new(DisplayRow(0), 5)
 9172            ]);
 9173        });
 9174    });
 9175    editor.update(cx, |editor, cx| {
 9176        assert_text_with_selections(editor, "let aˇ = 2;", cx);
 9177    });
 9178    editor.update_in(cx, |editor, window, cx| {
 9179        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9180    });
 9181    editor.update(cx, |editor, cx| {
 9182        assert_text_with_selections(editor, "let «ˇa» = 2;", cx);
 9183    });
 9184    editor.update_in(cx, |editor, window, cx| {
 9185        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9186    });
 9187    editor.update(cx, |editor, cx| {
 9188        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9189    });
 9190
 9191    // Test case 2: Cursor at end of statement
 9192    editor.update_in(cx, |editor, window, cx| {
 9193        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9194            s.select_display_ranges([
 9195                DisplayPoint::new(DisplayRow(0), 11)..DisplayPoint::new(DisplayRow(0), 11)
 9196            ]);
 9197        });
 9198    });
 9199    editor.update(cx, |editor, cx| {
 9200        assert_text_with_selections(editor, "let a = 2;ˇ", cx);
 9201    });
 9202    editor.update_in(cx, |editor, window, cx| {
 9203        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9204    });
 9205    editor.update(cx, |editor, cx| {
 9206        assert_text_with_selections(editor, "«ˇlet a = 2;»", cx);
 9207    });
 9208}
 9209
 9210#[gpui::test]
 9211async fn test_select_larger_syntax_node_for_cursor_at_symbol(cx: &mut TestAppContext) {
 9212    init_test(cx, |_| {});
 9213
 9214    let language = Arc::new(Language::new(
 9215        LanguageConfig {
 9216            name: "JavaScript".into(),
 9217            ..Default::default()
 9218        },
 9219        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
 9220    ));
 9221
 9222    let text = r#"
 9223        let a = {
 9224            key: "value",
 9225        };
 9226    "#
 9227    .unindent();
 9228
 9229    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
 9230    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9231    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9232
 9233    editor
 9234        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9235        .await;
 9236
 9237    // Test case 1: Cursor after '{'
 9238    editor.update_in(cx, |editor, window, cx| {
 9239        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9240            s.select_display_ranges([
 9241                DisplayPoint::new(DisplayRow(0), 9)..DisplayPoint::new(DisplayRow(0), 9)
 9242            ]);
 9243        });
 9244    });
 9245    editor.update(cx, |editor, cx| {
 9246        assert_text_with_selections(
 9247            editor,
 9248            indoc! {r#"
 9249                let a = {ˇ
 9250                    key: "value",
 9251                };
 9252            "#},
 9253            cx,
 9254        );
 9255    });
 9256    editor.update_in(cx, |editor, window, cx| {
 9257        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9258    });
 9259    editor.update(cx, |editor, cx| {
 9260        assert_text_with_selections(
 9261            editor,
 9262            indoc! {r#"
 9263                let a = «ˇ{
 9264                    key: "value",
 9265                }»;
 9266            "#},
 9267            cx,
 9268        );
 9269    });
 9270
 9271    // Test case 2: Cursor after ':'
 9272    editor.update_in(cx, |editor, window, cx| {
 9273        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9274            s.select_display_ranges([
 9275                DisplayPoint::new(DisplayRow(1), 8)..DisplayPoint::new(DisplayRow(1), 8)
 9276            ]);
 9277        });
 9278    });
 9279    editor.update(cx, |editor, cx| {
 9280        assert_text_with_selections(
 9281            editor,
 9282            indoc! {r#"
 9283                let a = {
 9284                    key:ˇ "value",
 9285                };
 9286            "#},
 9287            cx,
 9288        );
 9289    });
 9290    editor.update_in(cx, |editor, window, cx| {
 9291        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9292    });
 9293    editor.update(cx, |editor, cx| {
 9294        assert_text_with_selections(
 9295            editor,
 9296            indoc! {r#"
 9297                let a = {
 9298                    «ˇkey: "value"»,
 9299                };
 9300            "#},
 9301            cx,
 9302        );
 9303    });
 9304    editor.update_in(cx, |editor, window, cx| {
 9305        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9306    });
 9307    editor.update(cx, |editor, cx| {
 9308        assert_text_with_selections(
 9309            editor,
 9310            indoc! {r#"
 9311                let a = «ˇ{
 9312                    key: "value",
 9313                }»;
 9314            "#},
 9315            cx,
 9316        );
 9317    });
 9318
 9319    // Test case 3: Cursor after ','
 9320    editor.update_in(cx, |editor, window, cx| {
 9321        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9322            s.select_display_ranges([
 9323                DisplayPoint::new(DisplayRow(1), 17)..DisplayPoint::new(DisplayRow(1), 17)
 9324            ]);
 9325        });
 9326    });
 9327    editor.update(cx, |editor, cx| {
 9328        assert_text_with_selections(
 9329            editor,
 9330            indoc! {r#"
 9331                let a = {
 9332                    key: "value",ˇ
 9333                };
 9334            "#},
 9335            cx,
 9336        );
 9337    });
 9338    editor.update_in(cx, |editor, window, cx| {
 9339        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9340    });
 9341    editor.update(cx, |editor, cx| {
 9342        assert_text_with_selections(
 9343            editor,
 9344            indoc! {r#"
 9345                let a = «ˇ{
 9346                    key: "value",
 9347                }»;
 9348            "#},
 9349            cx,
 9350        );
 9351    });
 9352
 9353    // Test case 4: Cursor after ';'
 9354    editor.update_in(cx, |editor, window, cx| {
 9355        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9356            s.select_display_ranges([
 9357                DisplayPoint::new(DisplayRow(2), 2)..DisplayPoint::new(DisplayRow(2), 2)
 9358            ]);
 9359        });
 9360    });
 9361    editor.update(cx, |editor, cx| {
 9362        assert_text_with_selections(
 9363            editor,
 9364            indoc! {r#"
 9365                let a = {
 9366                    key: "value",
 9367                };ˇ
 9368            "#},
 9369            cx,
 9370        );
 9371    });
 9372    editor.update_in(cx, |editor, window, cx| {
 9373        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9374    });
 9375    editor.update(cx, |editor, cx| {
 9376        assert_text_with_selections(
 9377            editor,
 9378            indoc! {r#"
 9379                «ˇlet a = {
 9380                    key: "value",
 9381                };
 9382                »"#},
 9383            cx,
 9384        );
 9385    });
 9386}
 9387
 9388#[gpui::test]
 9389async fn test_select_larger_smaller_syntax_node_for_string(cx: &mut TestAppContext) {
 9390    init_test(cx, |_| {});
 9391
 9392    let language = Arc::new(Language::new(
 9393        LanguageConfig::default(),
 9394        Some(tree_sitter_rust::LANGUAGE.into()),
 9395    ));
 9396
 9397    let text = r#"
 9398        use mod1::mod2::{mod3, mod4};
 9399
 9400        fn fn_1(param1: bool, param2: &str) {
 9401            let var1 = "hello world";
 9402        }
 9403    "#
 9404    .unindent();
 9405
 9406    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
 9407    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9408    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9409
 9410    editor
 9411        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9412        .await;
 9413
 9414    // Test 1: Cursor on a letter of a string word
 9415    editor.update_in(cx, |editor, window, cx| {
 9416        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9417            s.select_display_ranges([
 9418                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 17)
 9419            ]);
 9420        });
 9421    });
 9422    editor.update_in(cx, |editor, window, cx| {
 9423        assert_text_with_selections(
 9424            editor,
 9425            indoc! {r#"
 9426                use mod1::mod2::{mod3, mod4};
 9427
 9428                fn fn_1(param1: bool, param2: &str) {
 9429                    let var1 = "hˇello world";
 9430                }
 9431            "#},
 9432            cx,
 9433        );
 9434        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9435        assert_text_with_selections(
 9436            editor,
 9437            indoc! {r#"
 9438                use mod1::mod2::{mod3, mod4};
 9439
 9440                fn fn_1(param1: bool, param2: &str) {
 9441                    let var1 = "«ˇhello» world";
 9442                }
 9443            "#},
 9444            cx,
 9445        );
 9446    });
 9447
 9448    // Test 2: Partial selection within a word
 9449    editor.update_in(cx, |editor, window, cx| {
 9450        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9451            s.select_display_ranges([
 9452                DisplayPoint::new(DisplayRow(3), 17)..DisplayPoint::new(DisplayRow(3), 19)
 9453            ]);
 9454        });
 9455    });
 9456    editor.update_in(cx, |editor, window, cx| {
 9457        assert_text_with_selections(
 9458            editor,
 9459            indoc! {r#"
 9460                use mod1::mod2::{mod3, mod4};
 9461
 9462                fn fn_1(param1: bool, param2: &str) {
 9463                    let var1 = "h«elˇ»lo world";
 9464                }
 9465            "#},
 9466            cx,
 9467        );
 9468        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9469        assert_text_with_selections(
 9470            editor,
 9471            indoc! {r#"
 9472                use mod1::mod2::{mod3, mod4};
 9473
 9474                fn fn_1(param1: bool, param2: &str) {
 9475                    let var1 = "«ˇhello» world";
 9476                }
 9477            "#},
 9478            cx,
 9479        );
 9480    });
 9481
 9482    // Test 3: Complete word already selected
 9483    editor.update_in(cx, |editor, window, cx| {
 9484        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9485            s.select_display_ranges([
 9486                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 21)
 9487            ]);
 9488        });
 9489    });
 9490    editor.update_in(cx, |editor, window, cx| {
 9491        assert_text_with_selections(
 9492            editor,
 9493            indoc! {r#"
 9494                use mod1::mod2::{mod3, mod4};
 9495
 9496                fn fn_1(param1: bool, param2: &str) {
 9497                    let var1 = "«helloˇ» world";
 9498                }
 9499            "#},
 9500            cx,
 9501        );
 9502        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9503        assert_text_with_selections(
 9504            editor,
 9505            indoc! {r#"
 9506                use mod1::mod2::{mod3, mod4};
 9507
 9508                fn fn_1(param1: bool, param2: &str) {
 9509                    let var1 = "«hello worldˇ»";
 9510                }
 9511            "#},
 9512            cx,
 9513        );
 9514    });
 9515
 9516    // Test 4: Selection spanning across words
 9517    editor.update_in(cx, |editor, window, cx| {
 9518        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9519            s.select_display_ranges([
 9520                DisplayPoint::new(DisplayRow(3), 19)..DisplayPoint::new(DisplayRow(3), 24)
 9521            ]);
 9522        });
 9523    });
 9524    editor.update_in(cx, |editor, window, cx| {
 9525        assert_text_with_selections(
 9526            editor,
 9527            indoc! {r#"
 9528                use mod1::mod2::{mod3, mod4};
 9529
 9530                fn fn_1(param1: bool, param2: &str) {
 9531                    let var1 = "hel«lo woˇ»rld";
 9532                }
 9533            "#},
 9534            cx,
 9535        );
 9536        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9537        assert_text_with_selections(
 9538            editor,
 9539            indoc! {r#"
 9540                use mod1::mod2::{mod3, mod4};
 9541
 9542                fn fn_1(param1: bool, param2: &str) {
 9543                    let var1 = "«ˇhello world»";
 9544                }
 9545            "#},
 9546            cx,
 9547        );
 9548    });
 9549
 9550    // Test 5: Expansion beyond string
 9551    editor.update_in(cx, |editor, window, cx| {
 9552        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9553        editor.select_larger_syntax_node(&SelectLargerSyntaxNode, window, cx);
 9554        assert_text_with_selections(
 9555            editor,
 9556            indoc! {r#"
 9557                use mod1::mod2::{mod3, mod4};
 9558
 9559                fn fn_1(param1: bool, param2: &str) {
 9560                    «ˇlet var1 = "hello world";»
 9561                }
 9562            "#},
 9563            cx,
 9564        );
 9565    });
 9566}
 9567
 9568#[gpui::test]
 9569async fn test_unwrap_syntax_nodes(cx: &mut gpui::TestAppContext) {
 9570    init_test(cx, |_| {});
 9571
 9572    let mut cx = EditorTestContext::new(cx).await;
 9573
 9574    let language = Arc::new(Language::new(
 9575        LanguageConfig::default(),
 9576        Some(tree_sitter_rust::LANGUAGE.into()),
 9577    ));
 9578
 9579    cx.update_buffer(|buffer, cx| {
 9580        buffer.set_language_immediate(Some(language), cx);
 9581    });
 9582
 9583    cx.set_state(indoc! { r#"use mod1::{mod2::{«mod3ˇ», mod4}, mod5::{mod6, «mod7ˇ»}};"# });
 9584    cx.update_editor(|editor, window, cx| {
 9585        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9586    });
 9587
 9588    cx.assert_editor_state(indoc! { r#"use mod1::{mod2::«mod3ˇ», mod5::«mod7ˇ»};"# });
 9589
 9590    cx.set_state(indoc! { r#"fn a() {
 9591          // what
 9592          // a
 9593          // ˇlong
 9594          // method
 9595          // I
 9596          // sure
 9597          // hope
 9598          // it
 9599          // works
 9600    }"# });
 9601
 9602    let buffer = cx.update_multibuffer(|multibuffer, _| multibuffer.as_singleton().unwrap());
 9603    let multi_buffer = cx.new(|_| MultiBuffer::new(Capability::ReadWrite));
 9604    cx.update(|_, cx| {
 9605        multi_buffer.update(cx, |multi_buffer, cx| {
 9606            multi_buffer.set_excerpts_for_path(
 9607                PathKey::for_buffer(&buffer, cx),
 9608                buffer,
 9609                [Point::new(1, 0)..Point::new(1, 0)],
 9610                3,
 9611                cx,
 9612            );
 9613        });
 9614    });
 9615
 9616    let editor2 = cx.new_window_entity(|window, cx| {
 9617        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
 9618    });
 9619
 9620    let mut cx = EditorTestContext::for_editor_in(editor2, &mut cx).await;
 9621    cx.update_editor(|editor, window, cx| {
 9622        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
 9623            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)]);
 9624        })
 9625    });
 9626
 9627    cx.assert_editor_state(indoc! { "
 9628        fn a() {
 9629              // what
 9630              // a
 9631        ˇ      // long
 9632              // method"});
 9633
 9634    cx.update_editor(|editor, window, cx| {
 9635        editor.unwrap_syntax_node(&UnwrapSyntaxNode, window, cx);
 9636    });
 9637
 9638    // Although we could potentially make the action work when the syntax node
 9639    // is half-hidden, it seems a bit dangerous as you can't easily tell what it
 9640    // did. Maybe we could also expand the excerpt to contain the range?
 9641    cx.assert_editor_state(indoc! { "
 9642        fn a() {
 9643              // what
 9644              // a
 9645        ˇ      // long
 9646              // method"});
 9647}
 9648
 9649#[gpui::test]
 9650async fn test_fold_function_bodies(cx: &mut TestAppContext) {
 9651    init_test(cx, |_| {});
 9652
 9653    let base_text = r#"
 9654        impl A {
 9655            // this is an uncommitted comment
 9656
 9657            fn b() {
 9658                c();
 9659            }
 9660
 9661            // this is another uncommitted comment
 9662
 9663            fn d() {
 9664                // e
 9665                // f
 9666            }
 9667        }
 9668
 9669        fn g() {
 9670            // h
 9671        }
 9672    "#
 9673    .unindent();
 9674
 9675    let text = r#"
 9676        ˇimpl A {
 9677
 9678            fn b() {
 9679                c();
 9680            }
 9681
 9682            fn d() {
 9683                // e
 9684                // f
 9685            }
 9686        }
 9687
 9688        fn g() {
 9689            // h
 9690        }
 9691    "#
 9692    .unindent();
 9693
 9694    let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
 9695    cx.set_state(&text);
 9696    cx.set_head_text(&base_text);
 9697    cx.update_editor(|editor, window, cx| {
 9698        editor.expand_all_diff_hunks(&Default::default(), window, cx);
 9699    });
 9700
 9701    cx.assert_state_with_diff(
 9702        "
 9703        ˇimpl A {
 9704      -     // this is an uncommitted comment
 9705
 9706            fn b() {
 9707                c();
 9708            }
 9709
 9710      -     // this is another uncommitted comment
 9711      -
 9712            fn d() {
 9713                // e
 9714                // f
 9715            }
 9716        }
 9717
 9718        fn g() {
 9719            // h
 9720        }
 9721    "
 9722        .unindent(),
 9723    );
 9724
 9725    let expected_display_text = "
 9726        impl A {
 9727            // this is an uncommitted comment
 9728
 9729            fn b() {
 9730 9731            }
 9732
 9733            // this is another uncommitted comment
 9734
 9735            fn d() {
 9736 9737            }
 9738        }
 9739
 9740        fn g() {
 9741 9742        }
 9743        "
 9744    .unindent();
 9745
 9746    cx.update_editor(|editor, window, cx| {
 9747        editor.fold_function_bodies(&FoldFunctionBodies, window, cx);
 9748        assert_eq!(editor.display_text(cx), expected_display_text);
 9749    });
 9750}
 9751
 9752#[gpui::test]
 9753async fn test_autoindent(cx: &mut TestAppContext) {
 9754    init_test(cx, |_| {});
 9755
 9756    let language = Arc::new(
 9757        Language::new(
 9758            LanguageConfig {
 9759                brackets: BracketPairConfig {
 9760                    pairs: vec![
 9761                        BracketPair {
 9762                            start: "{".to_string(),
 9763                            end: "}".to_string(),
 9764                            close: false,
 9765                            surround: false,
 9766                            newline: true,
 9767                        },
 9768                        BracketPair {
 9769                            start: "(".to_string(),
 9770                            end: ")".to_string(),
 9771                            close: false,
 9772                            surround: false,
 9773                            newline: true,
 9774                        },
 9775                    ],
 9776                    ..Default::default()
 9777                },
 9778                ..Default::default()
 9779            },
 9780            Some(tree_sitter_rust::LANGUAGE.into()),
 9781        )
 9782        .with_indents_query(
 9783            r#"
 9784                (_ "(" ")" @end) @indent
 9785                (_ "{" "}" @end) @indent
 9786            "#,
 9787        )
 9788        .unwrap(),
 9789    );
 9790
 9791    let text = "fn a() {}";
 9792
 9793    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
 9794    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9795    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9796    editor
 9797        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9798        .await;
 9799
 9800    editor.update_in(cx, |editor, window, cx| {
 9801        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9802            s.select_ranges([
 9803                MultiBufferOffset(5)..MultiBufferOffset(5),
 9804                MultiBufferOffset(8)..MultiBufferOffset(8),
 9805                MultiBufferOffset(9)..MultiBufferOffset(9),
 9806            ])
 9807        });
 9808        editor.newline(&Newline, window, cx);
 9809        assert_eq!(editor.text(cx), "fn a(\n    \n) {\n    \n}\n");
 9810        assert_eq!(
 9811            editor.selections.ranges(&editor.display_snapshot(cx)),
 9812            &[
 9813                Point::new(1, 4)..Point::new(1, 4),
 9814                Point::new(3, 4)..Point::new(3, 4),
 9815                Point::new(5, 0)..Point::new(5, 0)
 9816            ]
 9817        );
 9818    });
 9819}
 9820
 9821#[gpui::test]
 9822async fn test_autoindent_disabled(cx: &mut TestAppContext) {
 9823    init_test(cx, |settings| settings.defaults.auto_indent = Some(false));
 9824
 9825    let language = Arc::new(
 9826        Language::new(
 9827            LanguageConfig {
 9828                brackets: BracketPairConfig {
 9829                    pairs: vec![
 9830                        BracketPair {
 9831                            start: "{".to_string(),
 9832                            end: "}".to_string(),
 9833                            close: false,
 9834                            surround: false,
 9835                            newline: true,
 9836                        },
 9837                        BracketPair {
 9838                            start: "(".to_string(),
 9839                            end: ")".to_string(),
 9840                            close: false,
 9841                            surround: false,
 9842                            newline: true,
 9843                        },
 9844                    ],
 9845                    ..Default::default()
 9846                },
 9847                ..Default::default()
 9848            },
 9849            Some(tree_sitter_rust::LANGUAGE.into()),
 9850        )
 9851        .with_indents_query(
 9852            r#"
 9853                (_ "(" ")" @end) @indent
 9854                (_ "{" "}" @end) @indent
 9855            "#,
 9856        )
 9857        .unwrap(),
 9858    );
 9859
 9860    let text = "fn a() {}";
 9861
 9862    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
 9863    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
 9864    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
 9865    editor
 9866        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
 9867        .await;
 9868
 9869    editor.update_in(cx, |editor, window, cx| {
 9870        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
 9871            s.select_ranges([
 9872                MultiBufferOffset(5)..MultiBufferOffset(5),
 9873                MultiBufferOffset(8)..MultiBufferOffset(8),
 9874                MultiBufferOffset(9)..MultiBufferOffset(9),
 9875            ])
 9876        });
 9877        editor.newline(&Newline, window, cx);
 9878        assert_eq!(
 9879            editor.text(cx),
 9880            indoc!(
 9881                "
 9882                fn a(
 9883
 9884                ) {
 9885
 9886                }
 9887                "
 9888            )
 9889        );
 9890        assert_eq!(
 9891            editor.selections.ranges(&editor.display_snapshot(cx)),
 9892            &[
 9893                Point::new(1, 0)..Point::new(1, 0),
 9894                Point::new(3, 0)..Point::new(3, 0),
 9895                Point::new(5, 0)..Point::new(5, 0)
 9896            ]
 9897        );
 9898    });
 9899}
 9900
 9901#[gpui::test]
 9902async fn test_autoindent_disabled_with_nested_language(cx: &mut TestAppContext) {
 9903    init_test(cx, |settings| {
 9904        settings.defaults.auto_indent = Some(true);
 9905        settings.languages.0.insert(
 9906            "python".into(),
 9907            LanguageSettingsContent {
 9908                auto_indent: Some(false),
 9909                ..Default::default()
 9910            },
 9911        );
 9912    });
 9913
 9914    let mut cx = EditorTestContext::new(cx).await;
 9915
 9916    let injected_language = Arc::new(
 9917        Language::new(
 9918            LanguageConfig {
 9919                brackets: BracketPairConfig {
 9920                    pairs: vec![
 9921                        BracketPair {
 9922                            start: "{".to_string(),
 9923                            end: "}".to_string(),
 9924                            close: false,
 9925                            surround: false,
 9926                            newline: true,
 9927                        },
 9928                        BracketPair {
 9929                            start: "(".to_string(),
 9930                            end: ")".to_string(),
 9931                            close: true,
 9932                            surround: false,
 9933                            newline: true,
 9934                        },
 9935                    ],
 9936                    ..Default::default()
 9937                },
 9938                name: "python".into(),
 9939                ..Default::default()
 9940            },
 9941            Some(tree_sitter_python::LANGUAGE.into()),
 9942        )
 9943        .with_indents_query(
 9944            r#"
 9945                (_ "(" ")" @end) @indent
 9946                (_ "{" "}" @end) @indent
 9947            "#,
 9948        )
 9949        .unwrap(),
 9950    );
 9951
 9952    let language = Arc::new(
 9953        Language::new(
 9954            LanguageConfig {
 9955                brackets: BracketPairConfig {
 9956                    pairs: vec![
 9957                        BracketPair {
 9958                            start: "{".to_string(),
 9959                            end: "}".to_string(),
 9960                            close: false,
 9961                            surround: false,
 9962                            newline: true,
 9963                        },
 9964                        BracketPair {
 9965                            start: "(".to_string(),
 9966                            end: ")".to_string(),
 9967                            close: true,
 9968                            surround: false,
 9969                            newline: true,
 9970                        },
 9971                    ],
 9972                    ..Default::default()
 9973                },
 9974                name: LanguageName::new("rust"),
 9975                ..Default::default()
 9976            },
 9977            Some(tree_sitter_rust::LANGUAGE.into()),
 9978        )
 9979        .with_indents_query(
 9980            r#"
 9981                (_ "(" ")" @end) @indent
 9982                (_ "{" "}" @end) @indent
 9983            "#,
 9984        )
 9985        .unwrap()
 9986        .with_injection_query(
 9987            r#"
 9988            (macro_invocation
 9989                macro: (identifier) @_macro_name
 9990                (token_tree) @injection.content
 9991                (#set! injection.language "python"))
 9992           "#,
 9993        )
 9994        .unwrap(),
 9995    );
 9996
 9997    cx.language_registry().add(injected_language);
 9998    cx.language_registry().add(language.clone());
 9999
10000    cx.update_buffer(|buffer, cx| {
10001        buffer.set_language_immediate(Some(language), cx);
10002    });
10003
10004    cx.set_state(r#"struct A {ˇ}"#);
10005
10006    cx.update_editor(|editor, window, cx| {
10007        editor.newline(&Default::default(), window, cx);
10008    });
10009
10010    cx.assert_editor_state(indoc!(
10011        "struct A {
10012            ˇ
10013        }"
10014    ));
10015
10016    cx.set_state(r#"select_biased!(ˇ)"#);
10017
10018    cx.update_editor(|editor, window, cx| {
10019        editor.newline(&Default::default(), window, cx);
10020        editor.handle_input("def ", window, cx);
10021        editor.handle_input("(", window, cx);
10022        editor.newline(&Default::default(), window, cx);
10023        editor.handle_input("a", window, cx);
10024    });
10025
10026    cx.assert_editor_state(indoc!(
10027        "select_biased!(
10028        def (
1002910030        )
10031        )"
10032    ));
10033}
10034
10035#[gpui::test]
10036async fn test_autoindent_selections(cx: &mut TestAppContext) {
10037    init_test(cx, |_| {});
10038
10039    {
10040        let mut cx = EditorLspTestContext::new_rust(Default::default(), cx).await;
10041        cx.set_state(indoc! {"
10042            impl A {
10043
10044                fn b() {}
10045
10046            «fn c() {
10047
10048            }ˇ»
10049            }
10050        "});
10051
10052        cx.update_editor(|editor, window, cx| {
10053            editor.autoindent(&Default::default(), window, cx);
10054        });
10055
10056        cx.assert_editor_state(indoc! {"
10057            impl A {
10058
10059                fn b() {}
10060
10061                «fn c() {
10062
10063                }ˇ»
10064            }
10065        "});
10066    }
10067
10068    {
10069        let mut cx = EditorTestContext::new_multibuffer(
10070            cx,
10071            [indoc! { "
10072                impl A {
10073                «
10074                // a
10075                fn b(){}
10076                »
10077                «
10078                    }
10079                    fn c(){}
10080                »
10081            "}],
10082        );
10083
10084        let buffer = cx.update_editor(|editor, _, cx| {
10085            let buffer = editor.buffer().update(cx, |buffer, _| {
10086                buffer.all_buffers().iter().next().unwrap().clone()
10087            });
10088            buffer.update(cx, |buffer, cx| {
10089                buffer.set_language_immediate(Some(rust_lang()), cx)
10090            });
10091            buffer
10092        });
10093
10094        cx.run_until_parked();
10095        cx.update_editor(|editor, window, cx| {
10096            editor.select_all(&Default::default(), window, cx);
10097            editor.autoindent(&Default::default(), window, cx)
10098        });
10099        cx.run_until_parked();
10100
10101        cx.update(|_, cx| {
10102            assert_eq!(
10103                buffer.read(cx).text(),
10104                indoc! { "
10105                    impl A {
10106
10107                        // a
10108                        fn b(){}
10109
10110
10111                    }
10112                    fn c(){}
10113
10114                " }
10115            )
10116        });
10117    }
10118}
10119
10120#[gpui::test]
10121async fn test_autoclose_and_auto_surround_pairs(cx: &mut TestAppContext) {
10122    init_test(cx, |_| {});
10123
10124    let mut cx = EditorTestContext::new(cx).await;
10125
10126    let language = Arc::new(Language::new(
10127        LanguageConfig {
10128            brackets: BracketPairConfig {
10129                pairs: vec![
10130                    BracketPair {
10131                        start: "{".to_string(),
10132                        end: "}".to_string(),
10133                        close: true,
10134                        surround: true,
10135                        newline: true,
10136                    },
10137                    BracketPair {
10138                        start: "(".to_string(),
10139                        end: ")".to_string(),
10140                        close: true,
10141                        surround: true,
10142                        newline: true,
10143                    },
10144                    BracketPair {
10145                        start: "/*".to_string(),
10146                        end: " */".to_string(),
10147                        close: true,
10148                        surround: true,
10149                        newline: true,
10150                    },
10151                    BracketPair {
10152                        start: "[".to_string(),
10153                        end: "]".to_string(),
10154                        close: false,
10155                        surround: false,
10156                        newline: true,
10157                    },
10158                    BracketPair {
10159                        start: "\"".to_string(),
10160                        end: "\"".to_string(),
10161                        close: true,
10162                        surround: true,
10163                        newline: false,
10164                    },
10165                    BracketPair {
10166                        start: "<".to_string(),
10167                        end: ">".to_string(),
10168                        close: false,
10169                        surround: true,
10170                        newline: true,
10171                    },
10172                ],
10173                ..Default::default()
10174            },
10175            autoclose_before: "})]".to_string(),
10176            ..Default::default()
10177        },
10178        Some(tree_sitter_rust::LANGUAGE.into()),
10179    ));
10180
10181    cx.language_registry().add(language.clone());
10182    cx.update_buffer(|buffer, cx| {
10183        buffer.set_language_immediate(Some(language), cx);
10184    });
10185
10186    cx.set_state(
10187        &r#"
10188            🏀ˇ
10189            εˇ
10190            ❤️ˇ
10191        "#
10192        .unindent(),
10193    );
10194
10195    // autoclose multiple nested brackets at multiple cursors
10196    cx.update_editor(|editor, window, cx| {
10197        editor.handle_input("{", window, cx);
10198        editor.handle_input("{", window, cx);
10199        editor.handle_input("{", window, cx);
10200    });
10201    cx.assert_editor_state(
10202        &"
10203            🏀{{{ˇ}}}
10204            ε{{{ˇ}}}
10205            ❤️{{{ˇ}}}
10206        "
10207        .unindent(),
10208    );
10209
10210    // insert a different closing bracket
10211    cx.update_editor(|editor, window, cx| {
10212        editor.handle_input(")", window, cx);
10213    });
10214    cx.assert_editor_state(
10215        &"
10216            🏀{{{)ˇ}}}
10217            ε{{{)ˇ}}}
10218            ❤️{{{)ˇ}}}
10219        "
10220        .unindent(),
10221    );
10222
10223    // skip over the auto-closed brackets when typing a closing bracket
10224    cx.update_editor(|editor, window, cx| {
10225        editor.move_right(&MoveRight, window, cx);
10226        editor.handle_input("}", window, cx);
10227        editor.handle_input("}", window, cx);
10228        editor.handle_input("}", window, cx);
10229    });
10230    cx.assert_editor_state(
10231        &"
10232            🏀{{{)}}}}ˇ
10233            ε{{{)}}}}ˇ
10234            ❤️{{{)}}}}ˇ
10235        "
10236        .unindent(),
10237    );
10238
10239    // autoclose multi-character pairs
10240    cx.set_state(
10241        &"
10242            ˇ
10243            ˇ
10244        "
10245        .unindent(),
10246    );
10247    cx.update_editor(|editor, window, cx| {
10248        editor.handle_input("/", window, cx);
10249        editor.handle_input("*", window, cx);
10250    });
10251    cx.assert_editor_state(
10252        &"
10253            /*ˇ */
10254            /*ˇ */
10255        "
10256        .unindent(),
10257    );
10258
10259    // one cursor autocloses a multi-character pair, one cursor
10260    // does not autoclose.
10261    cx.set_state(
10262        &"
1026310264            ˇ
10265        "
10266        .unindent(),
10267    );
10268    cx.update_editor(|editor, window, cx| editor.handle_input("*", window, cx));
10269    cx.assert_editor_state(
10270        &"
10271            /*ˇ */
1027210273        "
10274        .unindent(),
10275    );
10276
10277    // Don't autoclose if the next character isn't whitespace and isn't
10278    // listed in the language's "autoclose_before" section.
10279    cx.set_state("ˇa b");
10280    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10281    cx.assert_editor_state("{ˇa b");
10282
10283    // Don't autoclose if `close` is false for the bracket pair
10284    cx.set_state("ˇ");
10285    cx.update_editor(|editor, window, cx| editor.handle_input("[", window, cx));
10286    cx.assert_editor_state("");
10287
10288    // Surround with brackets if text is selected
10289    cx.set_state("«aˇ» b");
10290    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10291    cx.assert_editor_state("{«aˇ»} b");
10292
10293    // Autoclose when not immediately after a word character
10294    cx.set_state("a ˇ");
10295    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10296    cx.assert_editor_state("a \"ˇ\"");
10297
10298    // Autoclose pair where the start and end characters are the same
10299    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10300    cx.assert_editor_state("a \"\"ˇ");
10301
10302    // Don't autoclose when immediately after a word character
10303    cx.set_state("");
10304    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10305    cx.assert_editor_state("a\"ˇ");
10306
10307    // Do autoclose when after a non-word character
10308    cx.set_state("");
10309    cx.update_editor(|editor, window, cx| editor.handle_input("\"", window, cx));
10310    cx.assert_editor_state("{\"ˇ\"");
10311
10312    // Non identical pairs autoclose regardless of preceding character
10313    cx.set_state("");
10314    cx.update_editor(|editor, window, cx| editor.handle_input("{", window, cx));
10315    cx.assert_editor_state("a{ˇ}");
10316
10317    // Don't autoclose pair if autoclose is disabled
10318    cx.set_state("ˇ");
10319    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10320    cx.assert_editor_state("");
10321
10322    // Surround with brackets if text is selected and auto_surround is enabled, even if autoclose is disabled
10323    cx.set_state("«aˇ» b");
10324    cx.update_editor(|editor, window, cx| editor.handle_input("<", window, cx));
10325    cx.assert_editor_state("<«aˇ»> b");
10326}
10327
10328#[gpui::test]
10329async fn test_always_treat_brackets_as_autoclosed_skip_over(cx: &mut TestAppContext) {
10330    init_test(cx, |settings| {
10331        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
10332    });
10333
10334    let mut cx = EditorTestContext::new(cx).await;
10335
10336    let language = Arc::new(Language::new(
10337        LanguageConfig {
10338            brackets: BracketPairConfig {
10339                pairs: vec![
10340                    BracketPair {
10341                        start: "{".to_string(),
10342                        end: "}".to_string(),
10343                        close: true,
10344                        surround: true,
10345                        newline: true,
10346                    },
10347                    BracketPair {
10348                        start: "(".to_string(),
10349                        end: ")".to_string(),
10350                        close: true,
10351                        surround: true,
10352                        newline: true,
10353                    },
10354                    BracketPair {
10355                        start: "[".to_string(),
10356                        end: "]".to_string(),
10357                        close: false,
10358                        surround: false,
10359                        newline: true,
10360                    },
10361                ],
10362                ..Default::default()
10363            },
10364            autoclose_before: "})]".to_string(),
10365            ..Default::default()
10366        },
10367        Some(tree_sitter_rust::LANGUAGE.into()),
10368    ));
10369
10370    cx.language_registry().add(language.clone());
10371    cx.update_buffer(|buffer, cx| {
10372        buffer.set_language_immediate(Some(language), cx);
10373    });
10374
10375    cx.set_state(
10376        &"
10377            ˇ
10378            ˇ
10379            ˇ
10380        "
10381        .unindent(),
10382    );
10383
10384    // ensure only matching closing brackets are skipped over
10385    cx.update_editor(|editor, window, cx| {
10386        editor.handle_input("}", window, cx);
10387        editor.move_left(&MoveLeft, window, cx);
10388        editor.handle_input(")", window, cx);
10389        editor.move_left(&MoveLeft, window, cx);
10390    });
10391    cx.assert_editor_state(
10392        &"
10393            ˇ)}
10394            ˇ)}
10395            ˇ)}
10396        "
10397        .unindent(),
10398    );
10399
10400    // skip-over closing brackets at multiple cursors
10401    cx.update_editor(|editor, window, cx| {
10402        editor.handle_input(")", window, cx);
10403        editor.handle_input("}", window, cx);
10404    });
10405    cx.assert_editor_state(
10406        &"
10407            )}ˇ
10408            )}ˇ
10409            )}ˇ
10410        "
10411        .unindent(),
10412    );
10413
10414    // ignore non-close brackets
10415    cx.update_editor(|editor, window, cx| {
10416        editor.handle_input("]", window, cx);
10417        editor.move_left(&MoveLeft, window, cx);
10418        editor.handle_input("]", window, cx);
10419    });
10420    cx.assert_editor_state(
10421        &"
10422            )}]ˇ]
10423            )}]ˇ]
10424            )}]ˇ]
10425        "
10426        .unindent(),
10427    );
10428}
10429
10430#[gpui::test]
10431async fn test_autoclose_with_embedded_language(cx: &mut TestAppContext) {
10432    init_test(cx, |_| {});
10433
10434    let mut cx = EditorTestContext::new(cx).await;
10435
10436    let html_language = Arc::new(
10437        Language::new(
10438            LanguageConfig {
10439                name: "HTML".into(),
10440                brackets: BracketPairConfig {
10441                    pairs: vec![
10442                        BracketPair {
10443                            start: "<".into(),
10444                            end: ">".into(),
10445                            close: true,
10446                            ..Default::default()
10447                        },
10448                        BracketPair {
10449                            start: "{".into(),
10450                            end: "}".into(),
10451                            close: true,
10452                            ..Default::default()
10453                        },
10454                        BracketPair {
10455                            start: "(".into(),
10456                            end: ")".into(),
10457                            close: true,
10458                            ..Default::default()
10459                        },
10460                    ],
10461                    ..Default::default()
10462                },
10463                autoclose_before: "})]>".into(),
10464                ..Default::default()
10465            },
10466            Some(tree_sitter_html::LANGUAGE.into()),
10467        )
10468        .with_injection_query(
10469            r#"
10470            (script_element
10471                (raw_text) @injection.content
10472                (#set! injection.language "javascript"))
10473            "#,
10474        )
10475        .unwrap(),
10476    );
10477
10478    let javascript_language = Arc::new(Language::new(
10479        LanguageConfig {
10480            name: "JavaScript".into(),
10481            brackets: BracketPairConfig {
10482                pairs: vec![
10483                    BracketPair {
10484                        start: "/*".into(),
10485                        end: " */".into(),
10486                        close: true,
10487                        ..Default::default()
10488                    },
10489                    BracketPair {
10490                        start: "{".into(),
10491                        end: "}".into(),
10492                        close: true,
10493                        ..Default::default()
10494                    },
10495                    BracketPair {
10496                        start: "(".into(),
10497                        end: ")".into(),
10498                        close: true,
10499                        ..Default::default()
10500                    },
10501                ],
10502                ..Default::default()
10503            },
10504            autoclose_before: "})]>".into(),
10505            ..Default::default()
10506        },
10507        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
10508    ));
10509
10510    cx.language_registry().add(html_language.clone());
10511    cx.language_registry().add(javascript_language);
10512    cx.executor().run_until_parked();
10513
10514    cx.update_buffer(|buffer, cx| {
10515        buffer.set_language_immediate(Some(html_language), cx);
10516    });
10517
10518    cx.set_state(
10519        &r#"
10520            <body>ˇ
10521                <script>
10522                    var x = 1;ˇ
10523                </script>
10524            </body>ˇ
10525        "#
10526        .unindent(),
10527    );
10528
10529    // Precondition: different languages are active at different locations.
10530    cx.update_editor(|editor, window, cx| {
10531        let snapshot = editor.snapshot(window, cx);
10532        let cursors = editor
10533            .selections
10534            .ranges::<MultiBufferOffset>(&editor.display_snapshot(cx));
10535        let languages = cursors
10536            .iter()
10537            .map(|c| snapshot.language_at(c.start).unwrap().name())
10538            .collect::<Vec<_>>();
10539        assert_eq!(
10540            languages,
10541            &["HTML".into(), "JavaScript".into(), "HTML".into()]
10542        );
10543    });
10544
10545    // Angle brackets autoclose in HTML, but not JavaScript.
10546    cx.update_editor(|editor, window, cx| {
10547        editor.handle_input("<", window, cx);
10548        editor.handle_input("a", window, cx);
10549    });
10550    cx.assert_editor_state(
10551        &r#"
10552            <body><aˇ>
10553                <script>
10554                    var x = 1;<aˇ
10555                </script>
10556            </body><aˇ>
10557        "#
10558        .unindent(),
10559    );
10560
10561    // Curly braces and parens autoclose in both HTML and JavaScript.
10562    cx.update_editor(|editor, window, cx| {
10563        editor.handle_input(" b=", window, cx);
10564        editor.handle_input("{", window, cx);
10565        editor.handle_input("c", window, cx);
10566        editor.handle_input("(", window, cx);
10567    });
10568    cx.assert_editor_state(
10569        &r#"
10570            <body><a b={c(ˇ)}>
10571                <script>
10572                    var x = 1;<a b={c(ˇ)}
10573                </script>
10574            </body><a b={c(ˇ)}>
10575        "#
10576        .unindent(),
10577    );
10578
10579    // Brackets that were already autoclosed are skipped.
10580    cx.update_editor(|editor, window, cx| {
10581        editor.handle_input(")", window, cx);
10582        editor.handle_input("d", window, cx);
10583        editor.handle_input("}", window, cx);
10584    });
10585    cx.assert_editor_state(
10586        &r#"
10587            <body><a b={c()d}ˇ>
10588                <script>
10589                    var x = 1;<a b={c()d}ˇ
10590                </script>
10591            </body><a b={c()d}ˇ>
10592        "#
10593        .unindent(),
10594    );
10595    cx.update_editor(|editor, window, cx| {
10596        editor.handle_input(">", window, cx);
10597    });
10598    cx.assert_editor_state(
10599        &r#"
10600            <body><a b={c()d}>ˇ
10601                <script>
10602                    var x = 1;<a b={c()d}>ˇ
10603                </script>
10604            </body><a b={c()d}>ˇ
10605        "#
10606        .unindent(),
10607    );
10608
10609    // Reset
10610    cx.set_state(
10611        &r#"
10612            <body>ˇ
10613                <script>
10614                    var x = 1;ˇ
10615                </script>
10616            </body>ˇ
10617        "#
10618        .unindent(),
10619    );
10620
10621    cx.update_editor(|editor, window, cx| {
10622        editor.handle_input("<", window, cx);
10623    });
10624    cx.assert_editor_state(
10625        &r#"
10626            <body><ˇ>
10627                <script>
10628                    var x = 1;<ˇ
10629                </script>
10630            </body><ˇ>
10631        "#
10632        .unindent(),
10633    );
10634
10635    // When backspacing, the closing angle brackets are removed.
10636    cx.update_editor(|editor, window, cx| {
10637        editor.backspace(&Backspace, window, cx);
10638    });
10639    cx.assert_editor_state(
10640        &r#"
10641            <body>ˇ
10642                <script>
10643                    var x = 1;ˇ
10644                </script>
10645            </body>ˇ
10646        "#
10647        .unindent(),
10648    );
10649
10650    // Block comments autoclose in JavaScript, but not HTML.
10651    cx.update_editor(|editor, window, cx| {
10652        editor.handle_input("/", window, cx);
10653        editor.handle_input("*", window, cx);
10654    });
10655    cx.assert_editor_state(
10656        &r#"
10657            <body>/*ˇ
10658                <script>
10659                    var x = 1;/*ˇ */
10660                </script>
10661            </body>/*ˇ
10662        "#
10663        .unindent(),
10664    );
10665}
10666
10667#[gpui::test]
10668async fn test_autoclose_with_overrides(cx: &mut TestAppContext) {
10669    init_test(cx, |_| {});
10670
10671    let mut cx = EditorTestContext::new(cx).await;
10672
10673    let rust_language = Arc::new(
10674        Language::new(
10675            LanguageConfig {
10676                name: "Rust".into(),
10677                brackets: serde_json::from_value(json!([
10678                    { "start": "{", "end": "}", "close": true, "newline": true },
10679                    { "start": "\"", "end": "\"", "close": true, "newline": false, "not_in": ["string"] },
10680                ]))
10681                .unwrap(),
10682                autoclose_before: "})]>".into(),
10683                ..Default::default()
10684            },
10685            Some(tree_sitter_rust::LANGUAGE.into()),
10686        )
10687        .with_override_query("(string_literal) @string")
10688        .unwrap(),
10689    );
10690
10691    cx.language_registry().add(rust_language.clone());
10692    cx.update_buffer(|buffer, cx| {
10693        buffer.set_language_immediate(Some(rust_language), cx);
10694    });
10695
10696    cx.set_state(
10697        &r#"
10698            let x = ˇ
10699        "#
10700        .unindent(),
10701    );
10702
10703    // Inserting a quotation mark. A closing quotation mark is automatically inserted.
10704    cx.update_editor(|editor, window, cx| {
10705        editor.handle_input("\"", window, cx);
10706    });
10707    cx.assert_editor_state(
10708        &r#"
10709            let x = "ˇ"
10710        "#
10711        .unindent(),
10712    );
10713
10714    // Inserting another quotation mark. The cursor moves across the existing
10715    // automatically-inserted quotation mark.
10716    cx.update_editor(|editor, window, cx| {
10717        editor.handle_input("\"", window, cx);
10718    });
10719    cx.assert_editor_state(
10720        &r#"
10721            let x = ""ˇ
10722        "#
10723        .unindent(),
10724    );
10725
10726    // Reset
10727    cx.set_state(
10728        &r#"
10729            let x = ˇ
10730        "#
10731        .unindent(),
10732    );
10733
10734    // Inserting a quotation mark inside of a string. A second quotation mark is not inserted.
10735    cx.update_editor(|editor, window, cx| {
10736        editor.handle_input("\"", window, cx);
10737        editor.handle_input(" ", window, cx);
10738        editor.move_left(&Default::default(), window, cx);
10739        editor.handle_input("\\", window, cx);
10740        editor.handle_input("\"", window, cx);
10741    });
10742    cx.assert_editor_state(
10743        &r#"
10744            let x = "\"ˇ "
10745        "#
10746        .unindent(),
10747    );
10748
10749    // Inserting a closing quotation mark at the position of an automatically-inserted quotation
10750    // mark. Nothing is inserted.
10751    cx.update_editor(|editor, window, cx| {
10752        editor.move_right(&Default::default(), window, cx);
10753        editor.handle_input("\"", window, cx);
10754    });
10755    cx.assert_editor_state(
10756        &r#"
10757            let x = "\" "ˇ
10758        "#
10759        .unindent(),
10760    );
10761}
10762
10763#[gpui::test]
10764async fn test_surround_with_pair(cx: &mut TestAppContext) {
10765    init_test(cx, |_| {});
10766
10767    let language = Arc::new(Language::new(
10768        LanguageConfig {
10769            brackets: BracketPairConfig {
10770                pairs: vec![
10771                    BracketPair {
10772                        start: "{".to_string(),
10773                        end: "}".to_string(),
10774                        close: true,
10775                        surround: true,
10776                        newline: true,
10777                    },
10778                    BracketPair {
10779                        start: "/* ".to_string(),
10780                        end: "*/".to_string(),
10781                        close: true,
10782                        surround: true,
10783                        ..Default::default()
10784                    },
10785                ],
10786                ..Default::default()
10787            },
10788            ..Default::default()
10789        },
10790        Some(tree_sitter_rust::LANGUAGE.into()),
10791    ));
10792
10793    let text = r#"
10794        a
10795        b
10796        c
10797    "#
10798    .unindent();
10799
10800    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
10801    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10802    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10803    editor
10804        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10805        .await;
10806
10807    editor.update_in(cx, |editor, window, cx| {
10808        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10809            s.select_display_ranges([
10810                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10811                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10812                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1),
10813            ])
10814        });
10815
10816        editor.handle_input("{", window, cx);
10817        editor.handle_input("{", window, cx);
10818        editor.handle_input("{", window, cx);
10819        assert_eq!(
10820            editor.text(cx),
10821            "
10822                {{{a}}}
10823                {{{b}}}
10824                {{{c}}}
10825            "
10826            .unindent()
10827        );
10828        assert_eq!(
10829            display_ranges(editor, cx),
10830            [
10831                DisplayPoint::new(DisplayRow(0), 3)..DisplayPoint::new(DisplayRow(0), 4),
10832                DisplayPoint::new(DisplayRow(1), 3)..DisplayPoint::new(DisplayRow(1), 4),
10833                DisplayPoint::new(DisplayRow(2), 3)..DisplayPoint::new(DisplayRow(2), 4)
10834            ]
10835        );
10836
10837        editor.undo(&Undo, window, cx);
10838        editor.undo(&Undo, window, cx);
10839        editor.undo(&Undo, window, cx);
10840        assert_eq!(
10841            editor.text(cx),
10842            "
10843                a
10844                b
10845                c
10846            "
10847            .unindent()
10848        );
10849        assert_eq!(
10850            display_ranges(editor, cx),
10851            [
10852                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10853                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10854                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10855            ]
10856        );
10857
10858        // Ensure inserting the first character of a multi-byte bracket pair
10859        // doesn't surround the selections with the bracket.
10860        editor.handle_input("/", window, cx);
10861        assert_eq!(
10862            editor.text(cx),
10863            "
10864                /
10865                /
10866                /
10867            "
10868            .unindent()
10869        );
10870        assert_eq!(
10871            display_ranges(editor, cx),
10872            [
10873                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10874                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10875                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10876            ]
10877        );
10878
10879        editor.undo(&Undo, window, cx);
10880        assert_eq!(
10881            editor.text(cx),
10882            "
10883                a
10884                b
10885                c
10886            "
10887            .unindent()
10888        );
10889        assert_eq!(
10890            display_ranges(editor, cx),
10891            [
10892                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 1),
10893                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 1),
10894                DisplayPoint::new(DisplayRow(2), 0)..DisplayPoint::new(DisplayRow(2), 1)
10895            ]
10896        );
10897
10898        // Ensure inserting the last character of a multi-byte bracket pair
10899        // doesn't surround the selections with the bracket.
10900        editor.handle_input("*", window, cx);
10901        assert_eq!(
10902            editor.text(cx),
10903            "
10904                *
10905                *
10906                *
10907            "
10908            .unindent()
10909        );
10910        assert_eq!(
10911            display_ranges(editor, cx),
10912            [
10913                DisplayPoint::new(DisplayRow(0), 1)..DisplayPoint::new(DisplayRow(0), 1),
10914                DisplayPoint::new(DisplayRow(1), 1)..DisplayPoint::new(DisplayRow(1), 1),
10915                DisplayPoint::new(DisplayRow(2), 1)..DisplayPoint::new(DisplayRow(2), 1)
10916            ]
10917        );
10918    });
10919}
10920
10921#[gpui::test]
10922async fn test_delete_autoclose_pair(cx: &mut TestAppContext) {
10923    init_test(cx, |_| {});
10924
10925    let language = Arc::new(Language::new(
10926        LanguageConfig {
10927            brackets: BracketPairConfig {
10928                pairs: vec![BracketPair {
10929                    start: "{".to_string(),
10930                    end: "}".to_string(),
10931                    close: true,
10932                    surround: true,
10933                    newline: true,
10934                }],
10935                ..Default::default()
10936            },
10937            autoclose_before: "}".to_string(),
10938            ..Default::default()
10939        },
10940        Some(tree_sitter_rust::LANGUAGE.into()),
10941    ));
10942
10943    let text = r#"
10944        a
10945        b
10946        c
10947    "#
10948    .unindent();
10949
10950    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
10951    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
10952    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
10953    editor
10954        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
10955        .await;
10956
10957    editor.update_in(cx, |editor, window, cx| {
10958        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
10959            s.select_ranges([
10960                Point::new(0, 1)..Point::new(0, 1),
10961                Point::new(1, 1)..Point::new(1, 1),
10962                Point::new(2, 1)..Point::new(2, 1),
10963            ])
10964        });
10965
10966        editor.handle_input("{", window, cx);
10967        editor.handle_input("{", window, cx);
10968        editor.handle_input("_", window, cx);
10969        assert_eq!(
10970            editor.text(cx),
10971            "
10972                a{{_}}
10973                b{{_}}
10974                c{{_}}
10975            "
10976            .unindent()
10977        );
10978        assert_eq!(
10979            editor
10980                .selections
10981                .ranges::<Point>(&editor.display_snapshot(cx)),
10982            [
10983                Point::new(0, 4)..Point::new(0, 4),
10984                Point::new(1, 4)..Point::new(1, 4),
10985                Point::new(2, 4)..Point::new(2, 4)
10986            ]
10987        );
10988
10989        editor.backspace(&Default::default(), window, cx);
10990        editor.backspace(&Default::default(), window, cx);
10991        assert_eq!(
10992            editor.text(cx),
10993            "
10994                a{}
10995                b{}
10996                c{}
10997            "
10998            .unindent()
10999        );
11000        assert_eq!(
11001            editor
11002                .selections
11003                .ranges::<Point>(&editor.display_snapshot(cx)),
11004            [
11005                Point::new(0, 2)..Point::new(0, 2),
11006                Point::new(1, 2)..Point::new(1, 2),
11007                Point::new(2, 2)..Point::new(2, 2)
11008            ]
11009        );
11010
11011        editor.delete_to_previous_word_start(&Default::default(), window, cx);
11012        assert_eq!(
11013            editor.text(cx),
11014            "
11015                a
11016                b
11017                c
11018            "
11019            .unindent()
11020        );
11021        assert_eq!(
11022            editor
11023                .selections
11024                .ranges::<Point>(&editor.display_snapshot(cx)),
11025            [
11026                Point::new(0, 1)..Point::new(0, 1),
11027                Point::new(1, 1)..Point::new(1, 1),
11028                Point::new(2, 1)..Point::new(2, 1)
11029            ]
11030        );
11031    });
11032}
11033
11034#[gpui::test]
11035async fn test_always_treat_brackets_as_autoclosed_delete(cx: &mut TestAppContext) {
11036    init_test(cx, |settings| {
11037        settings.defaults.always_treat_brackets_as_autoclosed = Some(true);
11038    });
11039
11040    let mut cx = EditorTestContext::new(cx).await;
11041
11042    let language = Arc::new(Language::new(
11043        LanguageConfig {
11044            brackets: BracketPairConfig {
11045                pairs: vec![
11046                    BracketPair {
11047                        start: "{".to_string(),
11048                        end: "}".to_string(),
11049                        close: true,
11050                        surround: true,
11051                        newline: true,
11052                    },
11053                    BracketPair {
11054                        start: "(".to_string(),
11055                        end: ")".to_string(),
11056                        close: true,
11057                        surround: true,
11058                        newline: true,
11059                    },
11060                    BracketPair {
11061                        start: "[".to_string(),
11062                        end: "]".to_string(),
11063                        close: false,
11064                        surround: true,
11065                        newline: true,
11066                    },
11067                ],
11068                ..Default::default()
11069            },
11070            autoclose_before: "})]".to_string(),
11071            ..Default::default()
11072        },
11073        Some(tree_sitter_rust::LANGUAGE.into()),
11074    ));
11075
11076    cx.language_registry().add(language.clone());
11077    cx.update_buffer(|buffer, cx| {
11078        buffer.set_language_immediate(Some(language), cx);
11079    });
11080
11081    cx.set_state(
11082        &"
11083            {(ˇ)}
11084            [[ˇ]]
11085            {(ˇ)}
11086        "
11087        .unindent(),
11088    );
11089
11090    cx.update_editor(|editor, window, cx| {
11091        editor.backspace(&Default::default(), window, cx);
11092        editor.backspace(&Default::default(), window, cx);
11093    });
11094
11095    cx.assert_editor_state(
11096        &"
11097            ˇ
11098            ˇ]]
11099            ˇ
11100        "
11101        .unindent(),
11102    );
11103
11104    cx.update_editor(|editor, window, cx| {
11105        editor.handle_input("{", window, cx);
11106        editor.handle_input("{", window, cx);
11107        editor.move_right(&MoveRight, window, cx);
11108        editor.move_right(&MoveRight, window, cx);
11109        editor.move_left(&MoveLeft, window, cx);
11110        editor.move_left(&MoveLeft, window, cx);
11111        editor.backspace(&Default::default(), window, cx);
11112    });
11113
11114    cx.assert_editor_state(
11115        &"
11116            {ˇ}
11117            {ˇ}]]
11118            {ˇ}
11119        "
11120        .unindent(),
11121    );
11122
11123    cx.update_editor(|editor, window, cx| {
11124        editor.backspace(&Default::default(), window, cx);
11125    });
11126
11127    cx.assert_editor_state(
11128        &"
11129            ˇ
11130            ˇ]]
11131            ˇ
11132        "
11133        .unindent(),
11134    );
11135}
11136
11137#[gpui::test]
11138async fn test_auto_replace_emoji_shortcode(cx: &mut TestAppContext) {
11139    init_test(cx, |_| {});
11140
11141    let language = Arc::new(Language::new(
11142        LanguageConfig::default(),
11143        Some(tree_sitter_rust::LANGUAGE.into()),
11144    ));
11145
11146    let buffer = cx.new(|cx| Buffer::local("", cx).with_language_immediate(language, cx));
11147    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11148    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11149    editor
11150        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
11151        .await;
11152
11153    editor.update_in(cx, |editor, window, cx| {
11154        editor.set_auto_replace_emoji_shortcode(true);
11155
11156        editor.handle_input("Hello ", window, cx);
11157        editor.handle_input(":wave", window, cx);
11158        assert_eq!(editor.text(cx), "Hello :wave".unindent());
11159
11160        editor.handle_input(":", window, cx);
11161        assert_eq!(editor.text(cx), "Hello 👋".unindent());
11162
11163        editor.handle_input(" :smile", window, cx);
11164        assert_eq!(editor.text(cx), "Hello 👋 :smile".unindent());
11165
11166        editor.handle_input(":", window, cx);
11167        assert_eq!(editor.text(cx), "Hello 👋 😄".unindent());
11168
11169        // Ensure shortcode gets replaced when it is part of a word that only consists of emojis
11170        editor.handle_input(":wave", window, cx);
11171        assert_eq!(editor.text(cx), "Hello 👋 😄:wave".unindent());
11172
11173        editor.handle_input(":", window, cx);
11174        assert_eq!(editor.text(cx), "Hello 👋 😄👋".unindent());
11175
11176        editor.handle_input(":1", window, cx);
11177        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1".unindent());
11178
11179        editor.handle_input(":", window, cx);
11180        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1:".unindent());
11181
11182        // Ensure shortcode does not get replaced when it is part of a word
11183        editor.handle_input(" Test:wave", window, cx);
11184        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave".unindent());
11185
11186        editor.handle_input(":", window, cx);
11187        assert_eq!(editor.text(cx), "Hello 👋 😄👋:1: Test:wave:".unindent());
11188
11189        editor.set_auto_replace_emoji_shortcode(false);
11190
11191        // Ensure shortcode does not get replaced when auto replace is off
11192        editor.handle_input(" :wave", window, cx);
11193        assert_eq!(
11194            editor.text(cx),
11195            "Hello 👋 😄👋:1: Test:wave: :wave".unindent()
11196        );
11197
11198        editor.handle_input(":", window, cx);
11199        assert_eq!(
11200            editor.text(cx),
11201            "Hello 👋 😄👋:1: Test:wave: :wave:".unindent()
11202        );
11203    });
11204}
11205
11206#[gpui::test]
11207async fn test_snippet_placeholder_choices(cx: &mut TestAppContext) {
11208    init_test(cx, |_| {});
11209
11210    let (text, insertion_ranges) = marked_text_ranges(
11211        indoc! {"
11212            ˇ
11213        "},
11214        false,
11215    );
11216
11217    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11218    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11219
11220    _ = editor.update_in(cx, |editor, window, cx| {
11221        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2").unwrap();
11222
11223        editor
11224            .insert_snippet(
11225                &insertion_ranges
11226                    .iter()
11227                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11228                    .collect::<Vec<_>>(),
11229                snippet,
11230                window,
11231                cx,
11232            )
11233            .unwrap();
11234
11235        fn assert(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11236            let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11237            assert_eq!(editor.text(cx), expected_text);
11238            assert_eq!(
11239                editor.selections.ranges(&editor.display_snapshot(cx)),
11240                selection_ranges
11241                    .iter()
11242                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11243                    .collect::<Vec<_>>()
11244            );
11245        }
11246
11247        assert(
11248            editor,
11249            cx,
11250            indoc! {"
11251            type «» =•
11252            "},
11253        );
11254
11255        assert!(editor.context_menu_visible(), "There should be a matches");
11256    });
11257}
11258
11259#[gpui::test]
11260async fn test_snippet_tabstop_navigation_with_placeholders(cx: &mut TestAppContext) {
11261    init_test(cx, |_| {});
11262
11263    fn assert_state(editor: &mut Editor, cx: &mut Context<Editor>, marked_text: &str) {
11264        let (expected_text, selection_ranges) = marked_text_ranges(marked_text, false);
11265        assert_eq!(editor.text(cx), expected_text);
11266        assert_eq!(
11267            editor.selections.ranges(&editor.display_snapshot(cx)),
11268            selection_ranges
11269                .iter()
11270                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11271                .collect::<Vec<_>>()
11272        );
11273    }
11274
11275    let (text, insertion_ranges) = marked_text_ranges(
11276        indoc! {"
11277            ˇ
11278        "},
11279        false,
11280    );
11281
11282    let buffer = cx.update(|cx| MultiBuffer::build_simple(&text, cx));
11283    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
11284
11285    _ = editor.update_in(cx, |editor, window, cx| {
11286        let snippet = Snippet::parse("type ${1|,i32,u32|} = $2; $3").unwrap();
11287
11288        editor
11289            .insert_snippet(
11290                &insertion_ranges
11291                    .iter()
11292                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
11293                    .collect::<Vec<_>>(),
11294                snippet,
11295                window,
11296                cx,
11297            )
11298            .unwrap();
11299
11300        assert_state(
11301            editor,
11302            cx,
11303            indoc! {"
11304            type «» = ;•
11305            "},
11306        );
11307
11308        assert!(
11309            editor.context_menu_visible(),
11310            "Context menu should be visible for placeholder choices"
11311        );
11312
11313        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11314
11315        assert_state(
11316            editor,
11317            cx,
11318            indoc! {"
11319            type  = «»;•
11320            "},
11321        );
11322
11323        assert!(
11324            !editor.context_menu_visible(),
11325            "Context menu should be hidden after moving to next tabstop"
11326        );
11327
11328        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11329
11330        assert_state(
11331            editor,
11332            cx,
11333            indoc! {"
11334            type  = ; ˇ
11335            "},
11336        );
11337
11338        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11339
11340        assert_state(
11341            editor,
11342            cx,
11343            indoc! {"
11344            type  = ; ˇ
11345            "},
11346        );
11347    });
11348
11349    _ = editor.update_in(cx, |editor, window, cx| {
11350        editor.select_all(&SelectAll, window, cx);
11351        editor.backspace(&Backspace, window, cx);
11352
11353        let snippet = Snippet::parse("fn ${1|,foo,bar|} = ${2:value}; $3").unwrap();
11354        let insertion_ranges = editor
11355            .selections
11356            .all(&editor.display_snapshot(cx))
11357            .iter()
11358            .map(|s| s.range())
11359            .collect::<Vec<_>>();
11360
11361        editor
11362            .insert_snippet(&insertion_ranges, snippet, window, cx)
11363            .unwrap();
11364
11365        assert_state(editor, cx, "fn «» = value;•");
11366
11367        assert!(
11368            editor.context_menu_visible(),
11369            "Context menu should be visible for placeholder choices"
11370        );
11371
11372        editor.next_snippet_tabstop(&NextSnippetTabstop, window, cx);
11373
11374        assert_state(editor, cx, "fn  = «valueˇ»;•");
11375
11376        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11377
11378        assert_state(editor, cx, "fn «» = value;•");
11379
11380        assert!(
11381            editor.context_menu_visible(),
11382            "Context menu should be visible again after returning to first tabstop"
11383        );
11384
11385        editor.previous_snippet_tabstop(&PreviousSnippetTabstop, window, cx);
11386
11387        assert_state(editor, cx, "fn «» = value;•");
11388    });
11389}
11390
11391#[gpui::test]
11392async fn test_snippets(cx: &mut TestAppContext) {
11393    init_test(cx, |_| {});
11394
11395    let mut cx = EditorTestContext::new(cx).await;
11396
11397    cx.set_state(indoc! {"
11398        a.ˇ b
11399        a.ˇ b
11400        a.ˇ b
11401    "});
11402
11403    cx.update_editor(|editor, window, cx| {
11404        let snippet = Snippet::parse("f(${1:one}, ${2:two}, ${1:three})$0").unwrap();
11405        let insertion_ranges = editor
11406            .selections
11407            .all(&editor.display_snapshot(cx))
11408            .iter()
11409            .map(|s| s.range())
11410            .collect::<Vec<_>>();
11411        editor
11412            .insert_snippet(&insertion_ranges, snippet, window, cx)
11413            .unwrap();
11414    });
11415
11416    cx.assert_editor_state(indoc! {"
11417        a.f(«oneˇ», two, «threeˇ») b
11418        a.f(«oneˇ», two, «threeˇ») b
11419        a.f(«oneˇ», two, «threeˇ») b
11420    "});
11421
11422    // Can't move earlier than the first tab stop
11423    cx.update_editor(|editor, window, cx| {
11424        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11425    });
11426    cx.assert_editor_state(indoc! {"
11427        a.f(«oneˇ», two, «threeˇ») b
11428        a.f(«oneˇ», two, «threeˇ») b
11429        a.f(«oneˇ», two, «threeˇ») b
11430    "});
11431
11432    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11433    cx.assert_editor_state(indoc! {"
11434        a.f(one, «twoˇ», three) b
11435        a.f(one, «twoˇ», three) b
11436        a.f(one, «twoˇ», three) b
11437    "});
11438
11439    cx.update_editor(|editor, window, cx| assert!(editor.move_to_prev_snippet_tabstop(window, cx)));
11440    cx.assert_editor_state(indoc! {"
11441        a.f(«oneˇ», two, «threeˇ») b
11442        a.f(«oneˇ», two, «threeˇ») b
11443        a.f(«oneˇ», two, «threeˇ») b
11444    "});
11445
11446    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11447    cx.assert_editor_state(indoc! {"
11448        a.f(one, «twoˇ», three) b
11449        a.f(one, «twoˇ», three) b
11450        a.f(one, «twoˇ», three) b
11451    "});
11452    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11453    cx.assert_editor_state(indoc! {"
11454        a.f(one, two, three)ˇ b
11455        a.f(one, two, three)ˇ b
11456        a.f(one, two, three)ˇ b
11457    "});
11458
11459    // As soon as the last tab stop is reached, snippet state is gone
11460    cx.update_editor(|editor, window, cx| {
11461        assert!(!editor.move_to_prev_snippet_tabstop(window, cx))
11462    });
11463    cx.assert_editor_state(indoc! {"
11464        a.f(one, two, three)ˇ b
11465        a.f(one, two, three)ˇ b
11466        a.f(one, two, three)ˇ b
11467    "});
11468}
11469
11470#[gpui::test]
11471async fn test_snippet_indentation(cx: &mut TestAppContext) {
11472    init_test(cx, |_| {});
11473
11474    let mut cx = EditorTestContext::new(cx).await;
11475
11476    cx.update_editor(|editor, window, cx| {
11477        let snippet = Snippet::parse(indoc! {"
11478            /*
11479             * Multiline comment with leading indentation
11480             *
11481             * $1
11482             */
11483            $0"})
11484        .unwrap();
11485        let insertion_ranges = editor
11486            .selections
11487            .all(&editor.display_snapshot(cx))
11488            .iter()
11489            .map(|s| s.range())
11490            .collect::<Vec<_>>();
11491        editor
11492            .insert_snippet(&insertion_ranges, snippet, window, cx)
11493            .unwrap();
11494    });
11495
11496    cx.assert_editor_state(indoc! {"
11497        /*
11498         * Multiline comment with leading indentation
11499         *
11500         * ˇ
11501         */
11502    "});
11503
11504    cx.update_editor(|editor, window, cx| assert!(editor.move_to_next_snippet_tabstop(window, cx)));
11505    cx.assert_editor_state(indoc! {"
11506        /*
11507         * Multiline comment with leading indentation
11508         *
11509         *•
11510         */
11511        ˇ"});
11512}
11513
11514#[gpui::test]
11515async fn test_snippet_with_multi_word_prefix(cx: &mut TestAppContext) {
11516    init_test(cx, |_| {});
11517
11518    let mut cx = EditorTestContext::new(cx).await;
11519    cx.update_editor(|editor, _, cx| {
11520        editor.project().unwrap().update(cx, |project, cx| {
11521            project.snippets().update(cx, |snippets, _cx| {
11522                let snippet = project::snippet_provider::Snippet {
11523                    prefix: vec!["multi word".to_string()],
11524                    body: "this is many words".to_string(),
11525                    description: Some("description".to_string()),
11526                    name: "multi-word snippet test".to_string(),
11527                };
11528                snippets.add_snippet_for_test(
11529                    None,
11530                    PathBuf::from("test_snippets.json"),
11531                    vec![Arc::new(snippet)],
11532                );
11533            });
11534        })
11535    });
11536
11537    for (input_to_simulate, should_match_snippet) in [
11538        ("m", true),
11539        ("m ", true),
11540        ("m w", true),
11541        ("aa m w", true),
11542        ("aa m g", false),
11543    ] {
11544        cx.set_state("ˇ");
11545        cx.simulate_input(input_to_simulate); // fails correctly
11546
11547        cx.update_editor(|editor, _, _| {
11548            let Some(CodeContextMenu::Completions(context_menu)) = &*editor.context_menu.borrow()
11549            else {
11550                assert!(!should_match_snippet); // no completions! don't even show the menu
11551                return;
11552            };
11553            assert!(context_menu.visible());
11554            let completions = context_menu.completions.borrow();
11555
11556            assert_eq!(!completions.is_empty(), should_match_snippet);
11557        });
11558    }
11559}
11560
11561#[gpui::test]
11562async fn test_document_format_during_save(cx: &mut TestAppContext) {
11563    init_test(cx, |_| {});
11564
11565    let fs = FakeFs::new(cx.executor());
11566    fs.insert_file(path!("/file.rs"), Default::default()).await;
11567
11568    let project = Project::test(fs, [path!("/file.rs").as_ref()], cx).await;
11569
11570    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11571    language_registry.add(rust_lang());
11572    let mut fake_servers = language_registry.register_fake_lsp(
11573        "Rust",
11574        FakeLspAdapter {
11575            capabilities: lsp::ServerCapabilities {
11576                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11577                ..Default::default()
11578            },
11579            ..Default::default()
11580        },
11581    );
11582
11583    let buffer = project
11584        .update(cx, |project, cx| {
11585            project.open_local_buffer(path!("/file.rs"), cx)
11586        })
11587        .await
11588        .unwrap();
11589
11590    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11591    let (editor, cx) = cx.add_window_view(|window, cx| {
11592        build_editor_with_project(project.clone(), buffer, window, cx)
11593    });
11594    editor.update_in(cx, |editor, window, cx| {
11595        editor.set_text("one\ntwo\nthree\n", window, cx)
11596    });
11597    assert!(cx.read(|cx| editor.is_dirty(cx)));
11598
11599    cx.executor().start_waiting();
11600    let fake_server = fake_servers.next().await.unwrap();
11601
11602    {
11603        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11604            move |params, _| async move {
11605                assert_eq!(
11606                    params.text_document.uri,
11607                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11608                );
11609                assert_eq!(params.options.tab_size, 4);
11610                Ok(Some(vec![lsp::TextEdit::new(
11611                    lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11612                    ", ".to_string(),
11613                )]))
11614            },
11615        );
11616        let save = editor
11617            .update_in(cx, |editor, window, cx| {
11618                editor.save(
11619                    SaveOptions {
11620                        format: true,
11621                        autosave: false,
11622                    },
11623                    project.clone(),
11624                    window,
11625                    cx,
11626                )
11627            })
11628            .unwrap();
11629        cx.executor().start_waiting();
11630        save.await;
11631
11632        assert_eq!(
11633            editor.update(cx, |editor, cx| editor.text(cx)),
11634            "one, two\nthree\n"
11635        );
11636        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11637    }
11638
11639    {
11640        editor.update_in(cx, |editor, window, cx| {
11641            editor.set_text("one\ntwo\nthree\n", window, cx)
11642        });
11643        assert!(cx.read(|cx| editor.is_dirty(cx)));
11644
11645        // Ensure we can still save even if formatting hangs.
11646        fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
11647            move |params, _| async move {
11648                assert_eq!(
11649                    params.text_document.uri,
11650                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11651                );
11652                futures::future::pending::<()>().await;
11653                unreachable!()
11654            },
11655        );
11656        let save = editor
11657            .update_in(cx, |editor, window, cx| {
11658                editor.save(
11659                    SaveOptions {
11660                        format: true,
11661                        autosave: false,
11662                    },
11663                    project.clone(),
11664                    window,
11665                    cx,
11666                )
11667            })
11668            .unwrap();
11669        cx.executor().advance_clock(super::FORMAT_TIMEOUT);
11670        cx.executor().start_waiting();
11671        save.await;
11672        assert_eq!(
11673            editor.update(cx, |editor, cx| editor.text(cx)),
11674            "one\ntwo\nthree\n"
11675        );
11676    }
11677
11678    // Set rust language override and assert overridden tabsize is sent to language server
11679    update_test_language_settings(cx, |settings| {
11680        settings.languages.0.insert(
11681            "Rust".into(),
11682            LanguageSettingsContent {
11683                tab_size: NonZeroU32::new(8),
11684                ..Default::default()
11685            },
11686        );
11687    });
11688
11689    {
11690        editor.update_in(cx, |editor, window, cx| {
11691            editor.set_text("somehting_new\n", window, cx)
11692        });
11693        assert!(cx.read(|cx| editor.is_dirty(cx)));
11694        let _formatting_request_signal = fake_server
11695            .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
11696                assert_eq!(
11697                    params.text_document.uri,
11698                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
11699                );
11700                assert_eq!(params.options.tab_size, 8);
11701                Ok(Some(vec![]))
11702            });
11703        let save = editor
11704            .update_in(cx, |editor, window, cx| {
11705                editor.save(
11706                    SaveOptions {
11707                        format: true,
11708                        autosave: false,
11709                    },
11710                    project.clone(),
11711                    window,
11712                    cx,
11713                )
11714            })
11715            .unwrap();
11716        cx.executor().start_waiting();
11717        save.await;
11718    }
11719}
11720
11721#[gpui::test]
11722async fn test_redo_after_noop_format(cx: &mut TestAppContext) {
11723    init_test(cx, |settings| {
11724        settings.defaults.ensure_final_newline_on_save = Some(false);
11725    });
11726
11727    let fs = FakeFs::new(cx.executor());
11728    fs.insert_file(path!("/file.txt"), "foo".into()).await;
11729
11730    let project = Project::test(fs, [path!("/file.txt").as_ref()], cx).await;
11731
11732    let buffer = project
11733        .update(cx, |project, cx| {
11734            project.open_local_buffer(path!("/file.txt"), cx)
11735        })
11736        .await
11737        .unwrap();
11738
11739    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
11740    let (editor, cx) = cx.add_window_view(|window, cx| {
11741        build_editor_with_project(project.clone(), buffer, window, cx)
11742    });
11743    editor.update_in(cx, |editor, window, cx| {
11744        editor.change_selections(SelectionEffects::default(), window, cx, |s| {
11745            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
11746        });
11747    });
11748    assert!(!cx.read(|cx| editor.is_dirty(cx)));
11749
11750    editor.update_in(cx, |editor, window, cx| {
11751        editor.handle_input("\n", window, cx)
11752    });
11753    cx.run_until_parked();
11754    save(&editor, &project, cx).await;
11755    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11756
11757    editor.update_in(cx, |editor, window, cx| {
11758        editor.undo(&Default::default(), window, cx);
11759    });
11760    save(&editor, &project, cx).await;
11761    assert_eq!("foo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11762
11763    editor.update_in(cx, |editor, window, cx| {
11764        editor.redo(&Default::default(), window, cx);
11765    });
11766    cx.run_until_parked();
11767    assert_eq!("\nfoo", editor.read_with(cx, |editor, cx| editor.text(cx)));
11768
11769    async fn save(editor: &Entity<Editor>, project: &Entity<Project>, cx: &mut VisualTestContext) {
11770        let save = editor
11771            .update_in(cx, |editor, window, cx| {
11772                editor.save(
11773                    SaveOptions {
11774                        format: true,
11775                        autosave: false,
11776                    },
11777                    project.clone(),
11778                    window,
11779                    cx,
11780                )
11781            })
11782            .unwrap();
11783        cx.executor().start_waiting();
11784        save.await;
11785        assert!(!cx.read(|cx| editor.is_dirty(cx)));
11786    }
11787}
11788
11789#[gpui::test]
11790async fn test_multibuffer_format_during_save(cx: &mut TestAppContext) {
11791    init_test(cx, |_| {});
11792
11793    let cols = 4;
11794    let rows = 10;
11795    let sample_text_1 = sample_text(rows, cols, 'a');
11796    assert_eq!(
11797        sample_text_1,
11798        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
11799    );
11800    let sample_text_2 = sample_text(rows, cols, 'l');
11801    assert_eq!(
11802        sample_text_2,
11803        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
11804    );
11805    let sample_text_3 = sample_text(rows, cols, 'v');
11806    assert_eq!(
11807        sample_text_3,
11808        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
11809    );
11810
11811    let fs = FakeFs::new(cx.executor());
11812    fs.insert_tree(
11813        path!("/a"),
11814        json!({
11815            "main.rs": sample_text_1,
11816            "other.rs": sample_text_2,
11817            "lib.rs": sample_text_3,
11818        }),
11819    )
11820    .await;
11821
11822    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
11823    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
11824    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
11825
11826    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
11827    language_registry.add(rust_lang());
11828    let mut fake_servers = language_registry.register_fake_lsp(
11829        "Rust",
11830        FakeLspAdapter {
11831            capabilities: lsp::ServerCapabilities {
11832                document_formatting_provider: Some(lsp::OneOf::Left(true)),
11833                ..Default::default()
11834            },
11835            ..Default::default()
11836        },
11837    );
11838
11839    let worktree = project.update(cx, |project, cx| {
11840        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
11841        assert_eq!(worktrees.len(), 1);
11842        worktrees.pop().unwrap()
11843    });
11844    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
11845
11846    let buffer_1 = project
11847        .update(cx, |project, cx| {
11848            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
11849        })
11850        .await
11851        .unwrap();
11852    let buffer_2 = project
11853        .update(cx, |project, cx| {
11854            project.open_buffer((worktree_id, rel_path("other.rs")), cx)
11855        })
11856        .await
11857        .unwrap();
11858    let buffer_3 = project
11859        .update(cx, |project, cx| {
11860            project.open_buffer((worktree_id, rel_path("lib.rs")), cx)
11861        })
11862        .await
11863        .unwrap();
11864
11865    let multi_buffer = cx.new(|cx| {
11866        let mut multi_buffer = MultiBuffer::new(ReadWrite);
11867        multi_buffer.push_excerpts(
11868            buffer_1.clone(),
11869            [
11870                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11871                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11872                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11873            ],
11874            cx,
11875        );
11876        multi_buffer.push_excerpts(
11877            buffer_2.clone(),
11878            [
11879                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11880                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11881                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11882            ],
11883            cx,
11884        );
11885        multi_buffer.push_excerpts(
11886            buffer_3.clone(),
11887            [
11888                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
11889                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
11890                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
11891            ],
11892            cx,
11893        );
11894        multi_buffer
11895    });
11896    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
11897        Editor::new(
11898            EditorMode::full(),
11899            multi_buffer,
11900            Some(project.clone()),
11901            window,
11902            cx,
11903        )
11904    });
11905
11906    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11907        editor.change_selections(
11908            SelectionEffects::scroll(Autoscroll::Next),
11909            window,
11910            cx,
11911            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
11912        );
11913        editor.insert("|one|two|three|", window, cx);
11914    });
11915    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11916    multi_buffer_editor.update_in(cx, |editor, window, cx| {
11917        editor.change_selections(
11918            SelectionEffects::scroll(Autoscroll::Next),
11919            window,
11920            cx,
11921            |s| s.select_ranges(Some(MultiBufferOffset(60)..MultiBufferOffset(70))),
11922        );
11923        editor.insert("|four|five|six|", window, cx);
11924    });
11925    assert!(cx.read(|cx| multi_buffer_editor.is_dirty(cx)));
11926
11927    // First two buffers should be edited, but not the third one.
11928    assert_eq!(
11929        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11930        "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}",
11931    );
11932    buffer_1.update(cx, |buffer, _| {
11933        assert!(buffer.is_dirty());
11934        assert_eq!(
11935            buffer.text(),
11936            "a|one|two|three|aa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj",
11937        )
11938    });
11939    buffer_2.update(cx, |buffer, _| {
11940        assert!(buffer.is_dirty());
11941        assert_eq!(
11942            buffer.text(),
11943            "llll\nmmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu",
11944        )
11945    });
11946    buffer_3.update(cx, |buffer, _| {
11947        assert!(!buffer.is_dirty());
11948        assert_eq!(buffer.text(), sample_text_3,)
11949    });
11950    cx.executor().run_until_parked();
11951
11952    cx.executor().start_waiting();
11953    let save = multi_buffer_editor
11954        .update_in(cx, |editor, window, cx| {
11955            editor.save(
11956                SaveOptions {
11957                    format: true,
11958                    autosave: false,
11959                },
11960                project.clone(),
11961                window,
11962                cx,
11963            )
11964        })
11965        .unwrap();
11966
11967    let fake_server = fake_servers.next().await.unwrap();
11968    fake_server
11969        .server
11970        .on_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
11971            Ok(Some(vec![lsp::TextEdit::new(
11972                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
11973                format!("[{} formatted]", params.text_document.uri),
11974            )]))
11975        })
11976        .detach();
11977    save.await;
11978
11979    // After multibuffer saving, only first two buffers should be reformatted, but not the third one (as it was not dirty).
11980    assert!(cx.read(|cx| !multi_buffer_editor.is_dirty(cx)));
11981    assert_eq!(
11982        multi_buffer_editor.update(cx, |editor, cx| editor.text(cx)),
11983        uri!(
11984            "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}"
11985        ),
11986    );
11987    buffer_1.update(cx, |buffer, _| {
11988        assert!(!buffer.is_dirty());
11989        assert_eq!(
11990            buffer.text(),
11991            uri!("a|o[file:///a/main.rs formatted]bbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj\n"),
11992        )
11993    });
11994    buffer_2.update(cx, |buffer, _| {
11995        assert!(!buffer.is_dirty());
11996        assert_eq!(
11997            buffer.text(),
11998            uri!("lll[file:///a/other.rs formatted]mmmm\nnnnn|four|five|six|oooo\npppp\nr\nssss\ntttt\nuuuu\n"),
11999        )
12000    });
12001    buffer_3.update(cx, |buffer, _| {
12002        assert!(!buffer.is_dirty());
12003        assert_eq!(buffer.text(), sample_text_3,)
12004    });
12005}
12006
12007#[gpui::test]
12008async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
12009    init_test(cx, |_| {});
12010
12011    let fs = FakeFs::new(cx.executor());
12012    fs.insert_tree(
12013        path!("/dir"),
12014        json!({
12015            "file1.rs": "fn main() { println!(\"hello\"); }",
12016            "file2.rs": "fn test() { println!(\"test\"); }",
12017            "file3.rs": "fn other() { println!(\"other\"); }\n",
12018        }),
12019    )
12020    .await;
12021
12022    let project = Project::test(fs.clone(), [path!("/dir").as_ref()], cx).await;
12023    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
12024    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
12025
12026    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12027    language_registry.add(rust_lang());
12028
12029    let worktree = project.update(cx, |project, cx| project.worktrees(cx).next().unwrap());
12030    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
12031
12032    // Open three buffers
12033    let buffer_1 = project
12034        .update(cx, |project, cx| {
12035            project.open_buffer((worktree_id, rel_path("file1.rs")), cx)
12036        })
12037        .await
12038        .unwrap();
12039    let buffer_2 = project
12040        .update(cx, |project, cx| {
12041            project.open_buffer((worktree_id, rel_path("file2.rs")), cx)
12042        })
12043        .await
12044        .unwrap();
12045    let buffer_3 = project
12046        .update(cx, |project, cx| {
12047            project.open_buffer((worktree_id, rel_path("file3.rs")), cx)
12048        })
12049        .await
12050        .unwrap();
12051
12052    // Create a multi-buffer with all three buffers
12053    let multi_buffer = cx.new(|cx| {
12054        let mut multi_buffer = MultiBuffer::new(ReadWrite);
12055        multi_buffer.push_excerpts(
12056            buffer_1.clone(),
12057            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12058            cx,
12059        );
12060        multi_buffer.push_excerpts(
12061            buffer_2.clone(),
12062            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12063            cx,
12064        );
12065        multi_buffer.push_excerpts(
12066            buffer_3.clone(),
12067            [ExcerptRange::new(Point::new(0, 0)..Point::new(1, 0))],
12068            cx,
12069        );
12070        multi_buffer
12071    });
12072
12073    let editor = cx.new_window_entity(|window, cx| {
12074        Editor::new(
12075            EditorMode::full(),
12076            multi_buffer,
12077            Some(project.clone()),
12078            window,
12079            cx,
12080        )
12081    });
12082
12083    // Edit only the first buffer
12084    editor.update_in(cx, |editor, window, cx| {
12085        editor.change_selections(
12086            SelectionEffects::scroll(Autoscroll::Next),
12087            window,
12088            cx,
12089            |s| s.select_ranges(Some(MultiBufferOffset(10)..MultiBufferOffset(10))),
12090        );
12091        editor.insert("// edited", window, cx);
12092    });
12093
12094    // Verify that only buffer 1 is dirty
12095    buffer_1.update(cx, |buffer, _| assert!(buffer.is_dirty()));
12096    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12097    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12098
12099    // Get write counts after file creation (files were created with initial content)
12100    // We expect each file to have been written once during creation
12101    let write_count_after_creation_1 = fs.write_count_for_path(path!("/dir/file1.rs"));
12102    let write_count_after_creation_2 = fs.write_count_for_path(path!("/dir/file2.rs"));
12103    let write_count_after_creation_3 = fs.write_count_for_path(path!("/dir/file3.rs"));
12104
12105    // Perform autosave
12106    let save_task = editor.update_in(cx, |editor, window, cx| {
12107        editor.save(
12108            SaveOptions {
12109                format: true,
12110                autosave: true,
12111            },
12112            project.clone(),
12113            window,
12114            cx,
12115        )
12116    });
12117    save_task.await.unwrap();
12118
12119    // Only the dirty buffer should have been saved
12120    assert_eq!(
12121        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12122        1,
12123        "Buffer 1 was dirty, so it should have been written once during autosave"
12124    );
12125    assert_eq!(
12126        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12127        0,
12128        "Buffer 2 was clean, so it should not have been written during autosave"
12129    );
12130    assert_eq!(
12131        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12132        0,
12133        "Buffer 3 was clean, so it should not have been written during autosave"
12134    );
12135
12136    // Verify buffer states after autosave
12137    buffer_1.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12138    buffer_2.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12139    buffer_3.update(cx, |buffer, _| assert!(!buffer.is_dirty()));
12140
12141    // Now perform a manual save (format = true)
12142    let save_task = editor.update_in(cx, |editor, window, cx| {
12143        editor.save(
12144            SaveOptions {
12145                format: true,
12146                autosave: false,
12147            },
12148            project.clone(),
12149            window,
12150            cx,
12151        )
12152    });
12153    save_task.await.unwrap();
12154
12155    // During manual save, clean buffers don't get written to disk
12156    // They just get did_save called for language server notifications
12157    assert_eq!(
12158        fs.write_count_for_path(path!("/dir/file1.rs")) - write_count_after_creation_1,
12159        1,
12160        "Buffer 1 should only have been written once total (during autosave, not manual save)"
12161    );
12162    assert_eq!(
12163        fs.write_count_for_path(path!("/dir/file2.rs")) - write_count_after_creation_2,
12164        0,
12165        "Buffer 2 should not have been written at all"
12166    );
12167    assert_eq!(
12168        fs.write_count_for_path(path!("/dir/file3.rs")) - write_count_after_creation_3,
12169        0,
12170        "Buffer 3 should not have been written at all"
12171    );
12172}
12173
12174async fn setup_range_format_test(
12175    cx: &mut TestAppContext,
12176) -> (
12177    Entity<Project>,
12178    Entity<Editor>,
12179    &mut gpui::VisualTestContext,
12180    lsp::FakeLanguageServer,
12181) {
12182    init_test(cx, |_| {});
12183
12184    let fs = FakeFs::new(cx.executor());
12185    fs.insert_file(path!("/file.rs"), Default::default()).await;
12186
12187    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12188
12189    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12190    language_registry.add(rust_lang());
12191    let mut fake_servers = language_registry.register_fake_lsp(
12192        "Rust",
12193        FakeLspAdapter {
12194            capabilities: lsp::ServerCapabilities {
12195                document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
12196                ..lsp::ServerCapabilities::default()
12197            },
12198            ..FakeLspAdapter::default()
12199        },
12200    );
12201
12202    let buffer = project
12203        .update(cx, |project, cx| {
12204            project.open_local_buffer(path!("/file.rs"), cx)
12205        })
12206        .await
12207        .unwrap();
12208
12209    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12210    let (editor, cx) = cx.add_window_view(|window, cx| {
12211        build_editor_with_project(project.clone(), buffer, window, cx)
12212    });
12213
12214    cx.executor().start_waiting();
12215    let fake_server = fake_servers.next().await.unwrap();
12216
12217    (project, editor, cx, fake_server)
12218}
12219
12220#[gpui::test]
12221async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
12222    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12223
12224    editor.update_in(cx, |editor, window, cx| {
12225        editor.set_text("one\ntwo\nthree\n", window, cx)
12226    });
12227    assert!(cx.read(|cx| editor.is_dirty(cx)));
12228
12229    let save = editor
12230        .update_in(cx, |editor, window, cx| {
12231            editor.save(
12232                SaveOptions {
12233                    format: true,
12234                    autosave: false,
12235                },
12236                project.clone(),
12237                window,
12238                cx,
12239            )
12240        })
12241        .unwrap();
12242    fake_server
12243        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12244            assert_eq!(
12245                params.text_document.uri,
12246                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12247            );
12248            assert_eq!(params.options.tab_size, 4);
12249            Ok(Some(vec![lsp::TextEdit::new(
12250                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12251                ", ".to_string(),
12252            )]))
12253        })
12254        .next()
12255        .await;
12256    cx.executor().start_waiting();
12257    save.await;
12258    assert_eq!(
12259        editor.update(cx, |editor, cx| editor.text(cx)),
12260        "one, two\nthree\n"
12261    );
12262    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12263}
12264
12265#[gpui::test]
12266async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
12267    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12268
12269    editor.update_in(cx, |editor, window, cx| {
12270        editor.set_text("one\ntwo\nthree\n", window, cx)
12271    });
12272    assert!(cx.read(|cx| editor.is_dirty(cx)));
12273
12274    // Test that save still works when formatting hangs
12275    fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
12276        move |params, _| async move {
12277            assert_eq!(
12278                params.text_document.uri,
12279                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12280            );
12281            futures::future::pending::<()>().await;
12282            unreachable!()
12283        },
12284    );
12285    let save = editor
12286        .update_in(cx, |editor, window, cx| {
12287            editor.save(
12288                SaveOptions {
12289                    format: true,
12290                    autosave: false,
12291                },
12292                project.clone(),
12293                window,
12294                cx,
12295            )
12296        })
12297        .unwrap();
12298    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12299    cx.executor().start_waiting();
12300    save.await;
12301    assert_eq!(
12302        editor.update(cx, |editor, cx| editor.text(cx)),
12303        "one\ntwo\nthree\n"
12304    );
12305    assert!(!cx.read(|cx| editor.is_dirty(cx)));
12306}
12307
12308#[gpui::test]
12309async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
12310    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12311
12312    // Buffer starts clean, no formatting should be requested
12313    let save = editor
12314        .update_in(cx, |editor, window, cx| {
12315            editor.save(
12316                SaveOptions {
12317                    format: false,
12318                    autosave: false,
12319                },
12320                project.clone(),
12321                window,
12322                cx,
12323            )
12324        })
12325        .unwrap();
12326    let _pending_format_request = fake_server
12327        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |_, _| async move {
12328            panic!("Should not be invoked");
12329        })
12330        .next();
12331    cx.executor().start_waiting();
12332    save.await;
12333    cx.run_until_parked();
12334}
12335
12336#[gpui::test]
12337async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
12338    let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
12339
12340    // Set Rust language override and assert overridden tabsize is sent to language server
12341    update_test_language_settings(cx, |settings| {
12342        settings.languages.0.insert(
12343            "Rust".into(),
12344            LanguageSettingsContent {
12345                tab_size: NonZeroU32::new(8),
12346                ..Default::default()
12347            },
12348        );
12349    });
12350
12351    editor.update_in(cx, |editor, window, cx| {
12352        editor.set_text("something_new\n", window, cx)
12353    });
12354    assert!(cx.read(|cx| editor.is_dirty(cx)));
12355    let save = editor
12356        .update_in(cx, |editor, window, cx| {
12357            editor.save(
12358                SaveOptions {
12359                    format: true,
12360                    autosave: false,
12361                },
12362                project.clone(),
12363                window,
12364                cx,
12365            )
12366        })
12367        .unwrap();
12368    fake_server
12369        .set_request_handler::<lsp::request::RangeFormatting, _, _>(move |params, _| async move {
12370            assert_eq!(
12371                params.text_document.uri,
12372                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12373            );
12374            assert_eq!(params.options.tab_size, 8);
12375            Ok(Some(Vec::new()))
12376        })
12377        .next()
12378        .await;
12379    save.await;
12380}
12381
12382#[gpui::test]
12383async fn test_document_format_manual_trigger(cx: &mut TestAppContext) {
12384    init_test(cx, |settings| {
12385        settings.defaults.formatter = Some(FormatterList::Single(Formatter::LanguageServer(
12386            settings::LanguageServerFormatterSpecifier::Current,
12387        )))
12388    });
12389
12390    let fs = FakeFs::new(cx.executor());
12391    fs.insert_file(path!("/file.rs"), Default::default()).await;
12392
12393    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12394
12395    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12396    language_registry.add(Arc::new(Language::new(
12397        LanguageConfig {
12398            name: "Rust".into(),
12399            matcher: LanguageMatcher {
12400                path_suffixes: vec!["rs".to_string()],
12401                ..Default::default()
12402            },
12403            ..LanguageConfig::default()
12404        },
12405        Some(tree_sitter_rust::LANGUAGE.into()),
12406    )));
12407    update_test_language_settings(cx, |settings| {
12408        // Enable Prettier formatting for the same buffer, and ensure
12409        // LSP is called instead of Prettier.
12410        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12411    });
12412    let mut fake_servers = language_registry.register_fake_lsp(
12413        "Rust",
12414        FakeLspAdapter {
12415            capabilities: lsp::ServerCapabilities {
12416                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12417                ..Default::default()
12418            },
12419            ..Default::default()
12420        },
12421    );
12422
12423    let buffer = project
12424        .update(cx, |project, cx| {
12425            project.open_local_buffer(path!("/file.rs"), cx)
12426        })
12427        .await
12428        .unwrap();
12429
12430    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12431    let (editor, cx) = cx.add_window_view(|window, cx| {
12432        build_editor_with_project(project.clone(), buffer, window, cx)
12433    });
12434    editor.update_in(cx, |editor, window, cx| {
12435        editor.set_text("one\ntwo\nthree\n", window, cx)
12436    });
12437
12438    cx.executor().start_waiting();
12439    let fake_server = fake_servers.next().await.unwrap();
12440
12441    let format = editor
12442        .update_in(cx, |editor, window, cx| {
12443            editor.perform_format(
12444                project.clone(),
12445                FormatTrigger::Manual,
12446                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12447                window,
12448                cx,
12449            )
12450        })
12451        .unwrap();
12452    fake_server
12453        .set_request_handler::<lsp::request::Formatting, _, _>(move |params, _| async move {
12454            assert_eq!(
12455                params.text_document.uri,
12456                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12457            );
12458            assert_eq!(params.options.tab_size, 4);
12459            Ok(Some(vec![lsp::TextEdit::new(
12460                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(1, 0)),
12461                ", ".to_string(),
12462            )]))
12463        })
12464        .next()
12465        .await;
12466    cx.executor().start_waiting();
12467    format.await;
12468    assert_eq!(
12469        editor.update(cx, |editor, cx| editor.text(cx)),
12470        "one, two\nthree\n"
12471    );
12472
12473    editor.update_in(cx, |editor, window, cx| {
12474        editor.set_text("one\ntwo\nthree\n", window, cx)
12475    });
12476    // Ensure we don't lock if formatting hangs.
12477    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12478        move |params, _| async move {
12479            assert_eq!(
12480                params.text_document.uri,
12481                lsp::Uri::from_file_path(path!("/file.rs")).unwrap()
12482            );
12483            futures::future::pending::<()>().await;
12484            unreachable!()
12485        },
12486    );
12487    let format = editor
12488        .update_in(cx, |editor, window, cx| {
12489            editor.perform_format(
12490                project,
12491                FormatTrigger::Manual,
12492                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12493                window,
12494                cx,
12495            )
12496        })
12497        .unwrap();
12498    cx.executor().advance_clock(super::FORMAT_TIMEOUT);
12499    cx.executor().start_waiting();
12500    format.await;
12501    assert_eq!(
12502        editor.update(cx, |editor, cx| editor.text(cx)),
12503        "one\ntwo\nthree\n"
12504    );
12505}
12506
12507#[gpui::test]
12508async fn test_multiple_formatters(cx: &mut TestAppContext) {
12509    init_test(cx, |settings| {
12510        settings.defaults.remove_trailing_whitespace_on_save = Some(true);
12511        settings.defaults.formatter = Some(FormatterList::Vec(vec![
12512            Formatter::LanguageServer(settings::LanguageServerFormatterSpecifier::Current),
12513            Formatter::CodeAction("code-action-1".into()),
12514            Formatter::CodeAction("code-action-2".into()),
12515        ]))
12516    });
12517
12518    let fs = FakeFs::new(cx.executor());
12519    fs.insert_file(path!("/file.rs"), "one  \ntwo   \nthree".into())
12520        .await;
12521
12522    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12523    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12524    language_registry.add(rust_lang());
12525
12526    let mut fake_servers = language_registry.register_fake_lsp(
12527        "Rust",
12528        FakeLspAdapter {
12529            capabilities: lsp::ServerCapabilities {
12530                document_formatting_provider: Some(lsp::OneOf::Left(true)),
12531                execute_command_provider: Some(lsp::ExecuteCommandOptions {
12532                    commands: vec!["the-command-for-code-action-1".into()],
12533                    ..Default::default()
12534                }),
12535                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12536                ..Default::default()
12537            },
12538            ..Default::default()
12539        },
12540    );
12541
12542    let buffer = project
12543        .update(cx, |project, cx| {
12544            project.open_local_buffer(path!("/file.rs"), cx)
12545        })
12546        .await
12547        .unwrap();
12548
12549    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12550    let (editor, cx) = cx.add_window_view(|window, cx| {
12551        build_editor_with_project(project.clone(), buffer, window, cx)
12552    });
12553
12554    cx.executor().start_waiting();
12555
12556    let fake_server = fake_servers.next().await.unwrap();
12557    fake_server.set_request_handler::<lsp::request::Formatting, _, _>(
12558        move |_params, _| async move {
12559            Ok(Some(vec![lsp::TextEdit::new(
12560                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12561                "applied-formatting\n".to_string(),
12562            )]))
12563        },
12564    );
12565    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12566        move |params, _| async move {
12567            let requested_code_actions = params.context.only.expect("Expected code action request");
12568            assert_eq!(requested_code_actions.len(), 1);
12569
12570            let uri = lsp::Uri::from_file_path(path!("/file.rs")).unwrap();
12571            let code_action = match requested_code_actions[0].as_str() {
12572                "code-action-1" => lsp::CodeAction {
12573                    kind: Some("code-action-1".into()),
12574                    edit: Some(lsp::WorkspaceEdit::new(
12575                        [(
12576                            uri,
12577                            vec![lsp::TextEdit::new(
12578                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12579                                "applied-code-action-1-edit\n".to_string(),
12580                            )],
12581                        )]
12582                        .into_iter()
12583                        .collect(),
12584                    )),
12585                    command: Some(lsp::Command {
12586                        command: "the-command-for-code-action-1".into(),
12587                        ..Default::default()
12588                    }),
12589                    ..Default::default()
12590                },
12591                "code-action-2" => lsp::CodeAction {
12592                    kind: Some("code-action-2".into()),
12593                    edit: Some(lsp::WorkspaceEdit::new(
12594                        [(
12595                            uri,
12596                            vec![lsp::TextEdit::new(
12597                                lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)),
12598                                "applied-code-action-2-edit\n".to_string(),
12599                            )],
12600                        )]
12601                        .into_iter()
12602                        .collect(),
12603                    )),
12604                    ..Default::default()
12605                },
12606                req => panic!("Unexpected code action request: {:?}", req),
12607            };
12608            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12609                code_action,
12610            )]))
12611        },
12612    );
12613
12614    fake_server.set_request_handler::<lsp::request::CodeActionResolveRequest, _, _>({
12615        move |params, _| async move { Ok(params) }
12616    });
12617
12618    let command_lock = Arc::new(futures::lock::Mutex::new(()));
12619    fake_server.set_request_handler::<lsp::request::ExecuteCommand, _, _>({
12620        let fake = fake_server.clone();
12621        let lock = command_lock.clone();
12622        move |params, _| {
12623            assert_eq!(params.command, "the-command-for-code-action-1");
12624            let fake = fake.clone();
12625            let lock = lock.clone();
12626            async move {
12627                lock.lock().await;
12628                fake.server
12629                    .request::<lsp::request::ApplyWorkspaceEdit>(lsp::ApplyWorkspaceEditParams {
12630                        label: None,
12631                        edit: lsp::WorkspaceEdit {
12632                            changes: Some(
12633                                [(
12634                                    lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
12635                                    vec![lsp::TextEdit {
12636                                        range: lsp::Range::new(
12637                                            lsp::Position::new(0, 0),
12638                                            lsp::Position::new(0, 0),
12639                                        ),
12640                                        new_text: "applied-code-action-1-command\n".into(),
12641                                    }],
12642                                )]
12643                                .into_iter()
12644                                .collect(),
12645                            ),
12646                            ..Default::default()
12647                        },
12648                    })
12649                    .await
12650                    .into_response()
12651                    .unwrap();
12652                Ok(Some(json!(null)))
12653            }
12654        }
12655    });
12656
12657    cx.executor().start_waiting();
12658    editor
12659        .update_in(cx, |editor, window, cx| {
12660            editor.perform_format(
12661                project.clone(),
12662                FormatTrigger::Manual,
12663                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12664                window,
12665                cx,
12666            )
12667        })
12668        .unwrap()
12669        .await;
12670    editor.update(cx, |editor, cx| {
12671        assert_eq!(
12672            editor.text(cx),
12673            r#"
12674                applied-code-action-2-edit
12675                applied-code-action-1-command
12676                applied-code-action-1-edit
12677                applied-formatting
12678                one
12679                two
12680                three
12681            "#
12682            .unindent()
12683        );
12684    });
12685
12686    editor.update_in(cx, |editor, window, cx| {
12687        editor.undo(&Default::default(), window, cx);
12688        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12689    });
12690
12691    // Perform a manual edit while waiting for an LSP command
12692    // that's being run as part of a formatting code action.
12693    let lock_guard = command_lock.lock().await;
12694    let format = editor
12695        .update_in(cx, |editor, window, cx| {
12696            editor.perform_format(
12697                project.clone(),
12698                FormatTrigger::Manual,
12699                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
12700                window,
12701                cx,
12702            )
12703        })
12704        .unwrap();
12705    cx.run_until_parked();
12706    editor.update(cx, |editor, cx| {
12707        assert_eq!(
12708            editor.text(cx),
12709            r#"
12710                applied-code-action-1-edit
12711                applied-formatting
12712                one
12713                two
12714                three
12715            "#
12716            .unindent()
12717        );
12718
12719        editor.buffer.update(cx, |buffer, cx| {
12720            let ix = buffer.len(cx);
12721            buffer.edit([(ix..ix, "edited\n")], None, cx);
12722        });
12723    });
12724
12725    // Allow the LSP command to proceed. Because the buffer was edited,
12726    // the second code action will not be run.
12727    drop(lock_guard);
12728    format.await;
12729    editor.update_in(cx, |editor, window, cx| {
12730        assert_eq!(
12731            editor.text(cx),
12732            r#"
12733                applied-code-action-1-command
12734                applied-code-action-1-edit
12735                applied-formatting
12736                one
12737                two
12738                three
12739                edited
12740            "#
12741            .unindent()
12742        );
12743
12744        // The manual edit is undone first, because it is the last thing the user did
12745        // (even though the command completed afterwards).
12746        editor.undo(&Default::default(), window, cx);
12747        assert_eq!(
12748            editor.text(cx),
12749            r#"
12750                applied-code-action-1-command
12751                applied-code-action-1-edit
12752                applied-formatting
12753                one
12754                two
12755                three
12756            "#
12757            .unindent()
12758        );
12759
12760        // All the formatting (including the command, which completed after the manual edit)
12761        // is undone together.
12762        editor.undo(&Default::default(), window, cx);
12763        assert_eq!(editor.text(cx), "one  \ntwo   \nthree");
12764    });
12765}
12766
12767#[gpui::test]
12768async fn test_organize_imports_manual_trigger(cx: &mut TestAppContext) {
12769    init_test(cx, |settings| {
12770        settings.defaults.formatter = Some(FormatterList::Vec(vec![Formatter::LanguageServer(
12771            settings::LanguageServerFormatterSpecifier::Current,
12772        )]))
12773    });
12774
12775    let fs = FakeFs::new(cx.executor());
12776    fs.insert_file(path!("/file.ts"), Default::default()).await;
12777
12778    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
12779
12780    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
12781    language_registry.add(Arc::new(Language::new(
12782        LanguageConfig {
12783            name: "TypeScript".into(),
12784            matcher: LanguageMatcher {
12785                path_suffixes: vec!["ts".to_string()],
12786                ..Default::default()
12787            },
12788            ..LanguageConfig::default()
12789        },
12790        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
12791    )));
12792    update_test_language_settings(cx, |settings| {
12793        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
12794    });
12795    let mut fake_servers = language_registry.register_fake_lsp(
12796        "TypeScript",
12797        FakeLspAdapter {
12798            capabilities: lsp::ServerCapabilities {
12799                code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
12800                ..Default::default()
12801            },
12802            ..Default::default()
12803        },
12804    );
12805
12806    let buffer = project
12807        .update(cx, |project, cx| {
12808            project.open_local_buffer(path!("/file.ts"), cx)
12809        })
12810        .await
12811        .unwrap();
12812
12813    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
12814    let (editor, cx) = cx.add_window_view(|window, cx| {
12815        build_editor_with_project(project.clone(), buffer, window, cx)
12816    });
12817    editor.update_in(cx, |editor, window, cx| {
12818        editor.set_text(
12819            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12820            window,
12821            cx,
12822        )
12823    });
12824
12825    cx.executor().start_waiting();
12826    let fake_server = fake_servers.next().await.unwrap();
12827
12828    let format = editor
12829        .update_in(cx, |editor, window, cx| {
12830            editor.perform_code_action_kind(
12831                project.clone(),
12832                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12833                window,
12834                cx,
12835            )
12836        })
12837        .unwrap();
12838    fake_server
12839        .set_request_handler::<lsp::request::CodeActionRequest, _, _>(move |params, _| async move {
12840            assert_eq!(
12841                params.text_document.uri,
12842                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12843            );
12844            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
12845                lsp::CodeAction {
12846                    title: "Organize Imports".to_string(),
12847                    kind: Some(lsp::CodeActionKind::SOURCE_ORGANIZE_IMPORTS),
12848                    edit: Some(lsp::WorkspaceEdit {
12849                        changes: Some(
12850                            [(
12851                                params.text_document.uri.clone(),
12852                                vec![lsp::TextEdit::new(
12853                                    lsp::Range::new(
12854                                        lsp::Position::new(1, 0),
12855                                        lsp::Position::new(2, 0),
12856                                    ),
12857                                    "".to_string(),
12858                                )],
12859                            )]
12860                            .into_iter()
12861                            .collect(),
12862                        ),
12863                        ..Default::default()
12864                    }),
12865                    ..Default::default()
12866                },
12867            )]))
12868        })
12869        .next()
12870        .await;
12871    cx.executor().start_waiting();
12872    format.await;
12873    assert_eq!(
12874        editor.update(cx, |editor, cx| editor.text(cx)),
12875        "import { a } from 'module';\n\nconst x = a;\n"
12876    );
12877
12878    editor.update_in(cx, |editor, window, cx| {
12879        editor.set_text(
12880            "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n",
12881            window,
12882            cx,
12883        )
12884    });
12885    // Ensure we don't lock if code action hangs.
12886    fake_server.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
12887        move |params, _| async move {
12888            assert_eq!(
12889                params.text_document.uri,
12890                lsp::Uri::from_file_path(path!("/file.ts")).unwrap()
12891            );
12892            futures::future::pending::<()>().await;
12893            unreachable!()
12894        },
12895    );
12896    let format = editor
12897        .update_in(cx, |editor, window, cx| {
12898            editor.perform_code_action_kind(
12899                project,
12900                CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
12901                window,
12902                cx,
12903            )
12904        })
12905        .unwrap();
12906    cx.executor().advance_clock(super::CODE_ACTION_TIMEOUT);
12907    cx.executor().start_waiting();
12908    format.await;
12909    assert_eq!(
12910        editor.update(cx, |editor, cx| editor.text(cx)),
12911        "import { a } from 'module';\nimport { b } from 'module';\n\nconst x = a;\n"
12912    );
12913}
12914
12915#[gpui::test]
12916async fn test_concurrent_format_requests(cx: &mut TestAppContext) {
12917    init_test(cx, |_| {});
12918
12919    let mut cx = EditorLspTestContext::new_rust(
12920        lsp::ServerCapabilities {
12921            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12922            ..Default::default()
12923        },
12924        cx,
12925    )
12926    .await;
12927
12928    cx.set_state(indoc! {"
12929        one.twoˇ
12930    "});
12931
12932    // The format request takes a long time. When it completes, it inserts
12933    // a newline and an indent before the `.`
12934    cx.lsp
12935        .set_request_handler::<lsp::request::Formatting, _, _>(move |_, cx| {
12936            let executor = cx.background_executor().clone();
12937            async move {
12938                executor.timer(Duration::from_millis(100)).await;
12939                Ok(Some(vec![lsp::TextEdit {
12940                    range: lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 3)),
12941                    new_text: "\n    ".into(),
12942                }]))
12943            }
12944        });
12945
12946    // Submit a format request.
12947    let format_1 = cx
12948        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12949        .unwrap();
12950    cx.executor().run_until_parked();
12951
12952    // Submit a second format request.
12953    let format_2 = cx
12954        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
12955        .unwrap();
12956    cx.executor().run_until_parked();
12957
12958    // Wait for both format requests to complete
12959    cx.executor().advance_clock(Duration::from_millis(200));
12960    cx.executor().start_waiting();
12961    format_1.await.unwrap();
12962    cx.executor().start_waiting();
12963    format_2.await.unwrap();
12964
12965    // The formatting edits only happens once.
12966    cx.assert_editor_state(indoc! {"
12967        one
12968            .twoˇ
12969    "});
12970}
12971
12972#[gpui::test]
12973async fn test_strip_whitespace_and_format_via_lsp(cx: &mut TestAppContext) {
12974    init_test(cx, |settings| {
12975        settings.defaults.formatter = Some(FormatterList::default())
12976    });
12977
12978    let mut cx = EditorLspTestContext::new_rust(
12979        lsp::ServerCapabilities {
12980            document_formatting_provider: Some(lsp::OneOf::Left(true)),
12981            ..Default::default()
12982        },
12983        cx,
12984    )
12985    .await;
12986
12987    // Record which buffer changes have been sent to the language server
12988    let buffer_changes = Arc::new(Mutex::new(Vec::new()));
12989    cx.lsp
12990        .handle_notification::<lsp::notification::DidChangeTextDocument, _>({
12991            let buffer_changes = buffer_changes.clone();
12992            move |params, _| {
12993                buffer_changes.lock().extend(
12994                    params
12995                        .content_changes
12996                        .into_iter()
12997                        .map(|e| (e.range.unwrap(), e.text)),
12998                );
12999            }
13000        });
13001    // Handle formatting requests to the language server.
13002    cx.lsp
13003        .set_request_handler::<lsp::request::Formatting, _, _>({
13004            let buffer_changes = buffer_changes.clone();
13005            move |_, _| {
13006                let buffer_changes = buffer_changes.clone();
13007                // Insert blank lines between each line of the buffer.
13008                async move {
13009                    // When formatting is requested, trailing whitespace has already been stripped,
13010                    // and the trailing newline has already been added.
13011                    assert_eq!(
13012                        &buffer_changes.lock()[1..],
13013                        &[
13014                            (
13015                                lsp::Range::new(lsp::Position::new(0, 3), lsp::Position::new(0, 4)),
13016                                "".into()
13017                            ),
13018                            (
13019                                lsp::Range::new(lsp::Position::new(2, 5), lsp::Position::new(2, 6)),
13020                                "".into()
13021                            ),
13022                            (
13023                                lsp::Range::new(lsp::Position::new(3, 4), lsp::Position::new(3, 4)),
13024                                "\n".into()
13025                            ),
13026                        ]
13027                    );
13028
13029                    Ok(Some(vec![
13030                        lsp::TextEdit {
13031                            range: lsp::Range::new(
13032                                lsp::Position::new(1, 0),
13033                                lsp::Position::new(1, 0),
13034                            ),
13035                            new_text: "\n".into(),
13036                        },
13037                        lsp::TextEdit {
13038                            range: lsp::Range::new(
13039                                lsp::Position::new(2, 0),
13040                                lsp::Position::new(2, 0),
13041                            ),
13042                            new_text: "\n".into(),
13043                        },
13044                    ]))
13045                }
13046            }
13047        });
13048
13049    // Set up a buffer white some trailing whitespace and no trailing newline.
13050    cx.set_state(
13051        &[
13052            "one ",   //
13053            "twoˇ",   //
13054            "three ", //
13055            "four",   //
13056        ]
13057        .join("\n"),
13058    );
13059    cx.run_until_parked();
13060
13061    // Submit a format request.
13062    let format = cx
13063        .update_editor(|editor, window, cx| editor.format(&Format, window, cx))
13064        .unwrap();
13065
13066    cx.run_until_parked();
13067    // After formatting the buffer, the trailing whitespace is stripped,
13068    // a newline is appended, and the edits provided by the language server
13069    // have been applied.
13070    format.await.unwrap();
13071
13072    cx.assert_editor_state(
13073        &[
13074            "one",   //
13075            "",      //
13076            "twoˇ",  //
13077            "",      //
13078            "three", //
13079            "four",  //
13080            "",      //
13081        ]
13082        .join("\n"),
13083    );
13084
13085    // Undoing the formatting undoes the trailing whitespace removal, the
13086    // trailing newline, and the LSP edits.
13087    cx.update_buffer(|buffer, cx| buffer.undo(cx));
13088    cx.assert_editor_state(
13089        &[
13090            "one ",   //
13091            "twoˇ",   //
13092            "three ", //
13093            "four",   //
13094        ]
13095        .join("\n"),
13096    );
13097}
13098
13099#[gpui::test]
13100async fn test_handle_input_for_show_signature_help_auto_signature_help_true(
13101    cx: &mut TestAppContext,
13102) {
13103    init_test(cx, |_| {});
13104
13105    cx.update(|cx| {
13106        cx.update_global::<SettingsStore, _>(|settings, cx| {
13107            settings.update_user_settings(cx, |settings| {
13108                settings.editor.auto_signature_help = Some(true);
13109            });
13110        });
13111    });
13112
13113    let mut cx = EditorLspTestContext::new_rust(
13114        lsp::ServerCapabilities {
13115            signature_help_provider: Some(lsp::SignatureHelpOptions {
13116                ..Default::default()
13117            }),
13118            ..Default::default()
13119        },
13120        cx,
13121    )
13122    .await;
13123
13124    let language = Language::new(
13125        LanguageConfig {
13126            name: "Rust".into(),
13127            brackets: BracketPairConfig {
13128                pairs: vec![
13129                    BracketPair {
13130                        start: "{".to_string(),
13131                        end: "}".to_string(),
13132                        close: true,
13133                        surround: true,
13134                        newline: true,
13135                    },
13136                    BracketPair {
13137                        start: "(".to_string(),
13138                        end: ")".to_string(),
13139                        close: true,
13140                        surround: true,
13141                        newline: true,
13142                    },
13143                    BracketPair {
13144                        start: "/*".to_string(),
13145                        end: " */".to_string(),
13146                        close: true,
13147                        surround: true,
13148                        newline: true,
13149                    },
13150                    BracketPair {
13151                        start: "[".to_string(),
13152                        end: "]".to_string(),
13153                        close: false,
13154                        surround: false,
13155                        newline: true,
13156                    },
13157                    BracketPair {
13158                        start: "\"".to_string(),
13159                        end: "\"".to_string(),
13160                        close: true,
13161                        surround: true,
13162                        newline: false,
13163                    },
13164                    BracketPair {
13165                        start: "<".to_string(),
13166                        end: ">".to_string(),
13167                        close: false,
13168                        surround: true,
13169                        newline: true,
13170                    },
13171                ],
13172                ..Default::default()
13173            },
13174            autoclose_before: "})]".to_string(),
13175            ..Default::default()
13176        },
13177        Some(tree_sitter_rust::LANGUAGE.into()),
13178    );
13179    let language = Arc::new(language);
13180
13181    cx.language_registry().add(language.clone());
13182    cx.update_buffer(|buffer, cx| {
13183        buffer.set_language_immediate(Some(language), cx);
13184    });
13185
13186    cx.set_state(
13187        &r#"
13188            fn main() {
13189                sampleˇ
13190            }
13191        "#
13192        .unindent(),
13193    );
13194
13195    cx.update_editor(|editor, window, cx| {
13196        editor.handle_input("(", window, cx);
13197    });
13198    cx.assert_editor_state(
13199        &"
13200            fn main() {
13201                sample(ˇ)
13202            }
13203        "
13204        .unindent(),
13205    );
13206
13207    let mocked_response = lsp::SignatureHelp {
13208        signatures: vec![lsp::SignatureInformation {
13209            label: "fn sample(param1: u8, param2: u8)".to_string(),
13210            documentation: None,
13211            parameters: Some(vec![
13212                lsp::ParameterInformation {
13213                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13214                    documentation: None,
13215                },
13216                lsp::ParameterInformation {
13217                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13218                    documentation: None,
13219                },
13220            ]),
13221            active_parameter: None,
13222        }],
13223        active_signature: Some(0),
13224        active_parameter: Some(0),
13225    };
13226    handle_signature_help_request(&mut cx, mocked_response).await;
13227
13228    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13229        .await;
13230
13231    cx.editor(|editor, _, _| {
13232        let signature_help_state = editor.signature_help_state.popover().cloned();
13233        let signature = signature_help_state.unwrap();
13234        assert_eq!(
13235            signature.signatures[signature.current_signature].label,
13236            "fn sample(param1: u8, param2: u8)"
13237        );
13238    });
13239}
13240
13241#[gpui::test]
13242async fn test_handle_input_with_different_show_signature_settings(cx: &mut TestAppContext) {
13243    init_test(cx, |_| {});
13244
13245    cx.update(|cx| {
13246        cx.update_global::<SettingsStore, _>(|settings, cx| {
13247            settings.update_user_settings(cx, |settings| {
13248                settings.editor.auto_signature_help = Some(false);
13249                settings.editor.show_signature_help_after_edits = Some(false);
13250            });
13251        });
13252    });
13253
13254    let mut cx = EditorLspTestContext::new_rust(
13255        lsp::ServerCapabilities {
13256            signature_help_provider: Some(lsp::SignatureHelpOptions {
13257                ..Default::default()
13258            }),
13259            ..Default::default()
13260        },
13261        cx,
13262    )
13263    .await;
13264
13265    let language = Language::new(
13266        LanguageConfig {
13267            name: "Rust".into(),
13268            brackets: BracketPairConfig {
13269                pairs: vec![
13270                    BracketPair {
13271                        start: "{".to_string(),
13272                        end: "}".to_string(),
13273                        close: true,
13274                        surround: true,
13275                        newline: true,
13276                    },
13277                    BracketPair {
13278                        start: "(".to_string(),
13279                        end: ")".to_string(),
13280                        close: true,
13281                        surround: true,
13282                        newline: true,
13283                    },
13284                    BracketPair {
13285                        start: "/*".to_string(),
13286                        end: " */".to_string(),
13287                        close: true,
13288                        surround: true,
13289                        newline: true,
13290                    },
13291                    BracketPair {
13292                        start: "[".to_string(),
13293                        end: "]".to_string(),
13294                        close: false,
13295                        surround: false,
13296                        newline: true,
13297                    },
13298                    BracketPair {
13299                        start: "\"".to_string(),
13300                        end: "\"".to_string(),
13301                        close: true,
13302                        surround: true,
13303                        newline: false,
13304                    },
13305                    BracketPair {
13306                        start: "<".to_string(),
13307                        end: ">".to_string(),
13308                        close: false,
13309                        surround: true,
13310                        newline: true,
13311                    },
13312                ],
13313                ..Default::default()
13314            },
13315            autoclose_before: "})]".to_string(),
13316            ..Default::default()
13317        },
13318        Some(tree_sitter_rust::LANGUAGE.into()),
13319    );
13320    let language = Arc::new(language);
13321
13322    cx.language_registry().add(language.clone());
13323    cx.update_buffer(|buffer, cx| {
13324        buffer.set_language_immediate(Some(language), cx);
13325    });
13326
13327    // Ensure that signature_help is not called when no signature help is enabled.
13328    cx.set_state(
13329        &r#"
13330            fn main() {
13331                sampleˇ
13332            }
13333        "#
13334        .unindent(),
13335    );
13336    cx.update_editor(|editor, window, cx| {
13337        editor.handle_input("(", window, cx);
13338    });
13339    cx.assert_editor_state(
13340        &"
13341            fn main() {
13342                sample(ˇ)
13343            }
13344        "
13345        .unindent(),
13346    );
13347    cx.editor(|editor, _, _| {
13348        assert!(editor.signature_help_state.task().is_none());
13349    });
13350
13351    let mocked_response = lsp::SignatureHelp {
13352        signatures: vec![lsp::SignatureInformation {
13353            label: "fn sample(param1: u8, param2: u8)".to_string(),
13354            documentation: None,
13355            parameters: Some(vec![
13356                lsp::ParameterInformation {
13357                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13358                    documentation: None,
13359                },
13360                lsp::ParameterInformation {
13361                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13362                    documentation: None,
13363                },
13364            ]),
13365            active_parameter: None,
13366        }],
13367        active_signature: Some(0),
13368        active_parameter: Some(0),
13369    };
13370
13371    // Ensure that signature_help is called when enabled afte edits
13372    cx.update(|_, cx| {
13373        cx.update_global::<SettingsStore, _>(|settings, cx| {
13374            settings.update_user_settings(cx, |settings| {
13375                settings.editor.auto_signature_help = Some(false);
13376                settings.editor.show_signature_help_after_edits = Some(true);
13377            });
13378        });
13379    });
13380    cx.set_state(
13381        &r#"
13382            fn main() {
13383                sampleˇ
13384            }
13385        "#
13386        .unindent(),
13387    );
13388    cx.update_editor(|editor, window, cx| {
13389        editor.handle_input("(", window, cx);
13390    });
13391    cx.assert_editor_state(
13392        &"
13393            fn main() {
13394                sample(ˇ)
13395            }
13396        "
13397        .unindent(),
13398    );
13399    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13400    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13401        .await;
13402    cx.update_editor(|editor, _, _| {
13403        let signature_help_state = editor.signature_help_state.popover().cloned();
13404        assert!(signature_help_state.is_some());
13405        let signature = signature_help_state.unwrap();
13406        assert_eq!(
13407            signature.signatures[signature.current_signature].label,
13408            "fn sample(param1: u8, param2: u8)"
13409        );
13410        editor.signature_help_state = SignatureHelpState::default();
13411    });
13412
13413    // Ensure that signature_help is called when auto signature help override is enabled
13414    cx.update(|_, cx| {
13415        cx.update_global::<SettingsStore, _>(|settings, cx| {
13416            settings.update_user_settings(cx, |settings| {
13417                settings.editor.auto_signature_help = Some(true);
13418                settings.editor.show_signature_help_after_edits = Some(false);
13419            });
13420        });
13421    });
13422    cx.set_state(
13423        &r#"
13424            fn main() {
13425                sampleˇ
13426            }
13427        "#
13428        .unindent(),
13429    );
13430    cx.update_editor(|editor, window, cx| {
13431        editor.handle_input("(", window, cx);
13432    });
13433    cx.assert_editor_state(
13434        &"
13435            fn main() {
13436                sample(ˇ)
13437            }
13438        "
13439        .unindent(),
13440    );
13441    handle_signature_help_request(&mut cx, mocked_response).await;
13442    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13443        .await;
13444    cx.editor(|editor, _, _| {
13445        let signature_help_state = editor.signature_help_state.popover().cloned();
13446        assert!(signature_help_state.is_some());
13447        let signature = signature_help_state.unwrap();
13448        assert_eq!(
13449            signature.signatures[signature.current_signature].label,
13450            "fn sample(param1: u8, param2: u8)"
13451        );
13452    });
13453}
13454
13455#[gpui::test]
13456async fn test_signature_help(cx: &mut TestAppContext) {
13457    init_test(cx, |_| {});
13458    cx.update(|cx| {
13459        cx.update_global::<SettingsStore, _>(|settings, cx| {
13460            settings.update_user_settings(cx, |settings| {
13461                settings.editor.auto_signature_help = Some(true);
13462            });
13463        });
13464    });
13465
13466    let mut cx = EditorLspTestContext::new_rust(
13467        lsp::ServerCapabilities {
13468            signature_help_provider: Some(lsp::SignatureHelpOptions {
13469                ..Default::default()
13470            }),
13471            ..Default::default()
13472        },
13473        cx,
13474    )
13475    .await;
13476
13477    // A test that directly calls `show_signature_help`
13478    cx.update_editor(|editor, window, cx| {
13479        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13480    });
13481
13482    let mocked_response = lsp::SignatureHelp {
13483        signatures: vec![lsp::SignatureInformation {
13484            label: "fn sample(param1: u8, param2: u8)".to_string(),
13485            documentation: None,
13486            parameters: Some(vec![
13487                lsp::ParameterInformation {
13488                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13489                    documentation: None,
13490                },
13491                lsp::ParameterInformation {
13492                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13493                    documentation: None,
13494                },
13495            ]),
13496            active_parameter: None,
13497        }],
13498        active_signature: Some(0),
13499        active_parameter: Some(0),
13500    };
13501    handle_signature_help_request(&mut cx, mocked_response).await;
13502
13503    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13504        .await;
13505
13506    cx.editor(|editor, _, _| {
13507        let signature_help_state = editor.signature_help_state.popover().cloned();
13508        assert!(signature_help_state.is_some());
13509        let signature = signature_help_state.unwrap();
13510        assert_eq!(
13511            signature.signatures[signature.current_signature].label,
13512            "fn sample(param1: u8, param2: u8)"
13513        );
13514    });
13515
13516    // When exiting outside from inside the brackets, `signature_help` is closed.
13517    cx.set_state(indoc! {"
13518        fn main() {
13519            sample(ˇ);
13520        }
13521
13522        fn sample(param1: u8, param2: u8) {}
13523    "});
13524
13525    cx.update_editor(|editor, window, cx| {
13526        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13527            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
13528        });
13529    });
13530
13531    let mocked_response = lsp::SignatureHelp {
13532        signatures: Vec::new(),
13533        active_signature: None,
13534        active_parameter: None,
13535    };
13536    handle_signature_help_request(&mut cx, mocked_response).await;
13537
13538    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13539        .await;
13540
13541    cx.editor(|editor, _, _| {
13542        assert!(!editor.signature_help_state.is_shown());
13543    });
13544
13545    // When entering inside the brackets from outside, `show_signature_help` is automatically called.
13546    cx.set_state(indoc! {"
13547        fn main() {
13548            sample(ˇ);
13549        }
13550
13551        fn sample(param1: u8, param2: u8) {}
13552    "});
13553
13554    let mocked_response = lsp::SignatureHelp {
13555        signatures: vec![lsp::SignatureInformation {
13556            label: "fn sample(param1: u8, param2: u8)".to_string(),
13557            documentation: None,
13558            parameters: Some(vec![
13559                lsp::ParameterInformation {
13560                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13561                    documentation: None,
13562                },
13563                lsp::ParameterInformation {
13564                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13565                    documentation: None,
13566                },
13567            ]),
13568            active_parameter: None,
13569        }],
13570        active_signature: Some(0),
13571        active_parameter: Some(0),
13572    };
13573    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13574    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13575        .await;
13576    cx.editor(|editor, _, _| {
13577        assert!(editor.signature_help_state.is_shown());
13578    });
13579
13580    // Restore the popover with more parameter input
13581    cx.set_state(indoc! {"
13582        fn main() {
13583            sample(param1, param2ˇ);
13584        }
13585
13586        fn sample(param1: u8, param2: u8) {}
13587    "});
13588
13589    let mocked_response = lsp::SignatureHelp {
13590        signatures: vec![lsp::SignatureInformation {
13591            label: "fn sample(param1: u8, param2: u8)".to_string(),
13592            documentation: None,
13593            parameters: Some(vec![
13594                lsp::ParameterInformation {
13595                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13596                    documentation: None,
13597                },
13598                lsp::ParameterInformation {
13599                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13600                    documentation: None,
13601                },
13602            ]),
13603            active_parameter: None,
13604        }],
13605        active_signature: Some(0),
13606        active_parameter: Some(1),
13607    };
13608    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13609    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13610        .await;
13611
13612    // When selecting a range, the popover is gone.
13613    // Avoid using `cx.set_state` to not actually edit the document, just change its selections.
13614    cx.update_editor(|editor, window, cx| {
13615        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13616            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13617        })
13618    });
13619    cx.assert_editor_state(indoc! {"
13620        fn main() {
13621            sample(param1, «ˇparam2»);
13622        }
13623
13624        fn sample(param1: u8, param2: u8) {}
13625    "});
13626    cx.editor(|editor, _, _| {
13627        assert!(!editor.signature_help_state.is_shown());
13628    });
13629
13630    // When unselecting again, the popover is back if within the brackets.
13631    cx.update_editor(|editor, window, cx| {
13632        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13633            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13634        })
13635    });
13636    cx.assert_editor_state(indoc! {"
13637        fn main() {
13638            sample(param1, ˇparam2);
13639        }
13640
13641        fn sample(param1: u8, param2: u8) {}
13642    "});
13643    handle_signature_help_request(&mut cx, mocked_response).await;
13644    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13645        .await;
13646    cx.editor(|editor, _, _| {
13647        assert!(editor.signature_help_state.is_shown());
13648    });
13649
13650    // Test to confirm that SignatureHelp does not appear after deselecting multiple ranges when it was hidden by pressing Escape.
13651    cx.update_editor(|editor, window, cx| {
13652        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13653            s.select_ranges(Some(Point::new(0, 0)..Point::new(0, 0)));
13654            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13655        })
13656    });
13657    cx.assert_editor_state(indoc! {"
13658        fn main() {
13659            sample(param1, ˇparam2);
13660        }
13661
13662        fn sample(param1: u8, param2: u8) {}
13663    "});
13664
13665    let mocked_response = lsp::SignatureHelp {
13666        signatures: vec![lsp::SignatureInformation {
13667            label: "fn sample(param1: u8, param2: u8)".to_string(),
13668            documentation: None,
13669            parameters: Some(vec![
13670                lsp::ParameterInformation {
13671                    label: lsp::ParameterLabel::Simple("param1: u8".to_string()),
13672                    documentation: None,
13673                },
13674                lsp::ParameterInformation {
13675                    label: lsp::ParameterLabel::Simple("param2: u8".to_string()),
13676                    documentation: None,
13677                },
13678            ]),
13679            active_parameter: None,
13680        }],
13681        active_signature: Some(0),
13682        active_parameter: Some(1),
13683    };
13684    handle_signature_help_request(&mut cx, mocked_response.clone()).await;
13685    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13686        .await;
13687    cx.update_editor(|editor, _, cx| {
13688        editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
13689    });
13690    cx.condition(|editor, _| !editor.signature_help_state.is_shown())
13691        .await;
13692    cx.update_editor(|editor, window, cx| {
13693        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13694            s.select_ranges(Some(Point::new(1, 25)..Point::new(1, 19)));
13695        })
13696    });
13697    cx.assert_editor_state(indoc! {"
13698        fn main() {
13699            sample(param1, «ˇparam2»);
13700        }
13701
13702        fn sample(param1: u8, param2: u8) {}
13703    "});
13704    cx.update_editor(|editor, window, cx| {
13705        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13706            s.select_ranges(Some(Point::new(1, 19)..Point::new(1, 19)));
13707        })
13708    });
13709    cx.assert_editor_state(indoc! {"
13710        fn main() {
13711            sample(param1, ˇparam2);
13712        }
13713
13714        fn sample(param1: u8, param2: u8) {}
13715    "});
13716    cx.condition(|editor, _| !editor.signature_help_state.is_shown()) // because hidden by escape
13717        .await;
13718}
13719
13720#[gpui::test]
13721async fn test_signature_help_multiple_signatures(cx: &mut TestAppContext) {
13722    init_test(cx, |_| {});
13723
13724    let mut cx = EditorLspTestContext::new_rust(
13725        lsp::ServerCapabilities {
13726            signature_help_provider: Some(lsp::SignatureHelpOptions {
13727                ..Default::default()
13728            }),
13729            ..Default::default()
13730        },
13731        cx,
13732    )
13733    .await;
13734
13735    cx.set_state(indoc! {"
13736        fn main() {
13737            overloadedˇ
13738        }
13739    "});
13740
13741    cx.update_editor(|editor, window, cx| {
13742        editor.handle_input("(", window, cx);
13743        editor.show_signature_help(&ShowSignatureHelp, window, cx);
13744    });
13745
13746    // Mock response with 3 signatures
13747    let mocked_response = lsp::SignatureHelp {
13748        signatures: vec![
13749            lsp::SignatureInformation {
13750                label: "fn overloaded(x: i32)".to_string(),
13751                documentation: None,
13752                parameters: Some(vec![lsp::ParameterInformation {
13753                    label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13754                    documentation: None,
13755                }]),
13756                active_parameter: None,
13757            },
13758            lsp::SignatureInformation {
13759                label: "fn overloaded(x: i32, y: i32)".to_string(),
13760                documentation: None,
13761                parameters: Some(vec![
13762                    lsp::ParameterInformation {
13763                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13764                        documentation: None,
13765                    },
13766                    lsp::ParameterInformation {
13767                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13768                        documentation: None,
13769                    },
13770                ]),
13771                active_parameter: None,
13772            },
13773            lsp::SignatureInformation {
13774                label: "fn overloaded(x: i32, y: i32, z: i32)".to_string(),
13775                documentation: None,
13776                parameters: Some(vec![
13777                    lsp::ParameterInformation {
13778                        label: lsp::ParameterLabel::Simple("x: i32".to_string()),
13779                        documentation: None,
13780                    },
13781                    lsp::ParameterInformation {
13782                        label: lsp::ParameterLabel::Simple("y: i32".to_string()),
13783                        documentation: None,
13784                    },
13785                    lsp::ParameterInformation {
13786                        label: lsp::ParameterLabel::Simple("z: i32".to_string()),
13787                        documentation: None,
13788                    },
13789                ]),
13790                active_parameter: None,
13791            },
13792        ],
13793        active_signature: Some(1),
13794        active_parameter: Some(0),
13795    };
13796    handle_signature_help_request(&mut cx, mocked_response).await;
13797
13798    cx.condition(|editor, _| editor.signature_help_state.is_shown())
13799        .await;
13800
13801    // Verify we have multiple signatures and the right one is selected
13802    cx.editor(|editor, _, _| {
13803        let popover = editor.signature_help_state.popover().cloned().unwrap();
13804        assert_eq!(popover.signatures.len(), 3);
13805        // active_signature was 1, so that should be the current
13806        assert_eq!(popover.current_signature, 1);
13807        assert_eq!(popover.signatures[0].label, "fn overloaded(x: i32)");
13808        assert_eq!(popover.signatures[1].label, "fn overloaded(x: i32, y: i32)");
13809        assert_eq!(
13810            popover.signatures[2].label,
13811            "fn overloaded(x: i32, y: i32, z: i32)"
13812        );
13813    });
13814
13815    // Test navigation functionality
13816    cx.update_editor(|editor, window, cx| {
13817        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13818    });
13819
13820    cx.editor(|editor, _, _| {
13821        let popover = editor.signature_help_state.popover().cloned().unwrap();
13822        assert_eq!(popover.current_signature, 2);
13823    });
13824
13825    // Test wrap around
13826    cx.update_editor(|editor, window, cx| {
13827        editor.signature_help_next(&crate::SignatureHelpNext, window, cx);
13828    });
13829
13830    cx.editor(|editor, _, _| {
13831        let popover = editor.signature_help_state.popover().cloned().unwrap();
13832        assert_eq!(popover.current_signature, 0);
13833    });
13834
13835    // Test previous navigation
13836    cx.update_editor(|editor, window, cx| {
13837        editor.signature_help_prev(&crate::SignatureHelpPrevious, window, cx);
13838    });
13839
13840    cx.editor(|editor, _, _| {
13841        let popover = editor.signature_help_state.popover().cloned().unwrap();
13842        assert_eq!(popover.current_signature, 2);
13843    });
13844}
13845
13846#[gpui::test]
13847async fn test_completion_mode(cx: &mut TestAppContext) {
13848    init_test(cx, |_| {});
13849    let mut cx = EditorLspTestContext::new_rust(
13850        lsp::ServerCapabilities {
13851            completion_provider: Some(lsp::CompletionOptions {
13852                resolve_provider: Some(true),
13853                ..Default::default()
13854            }),
13855            ..Default::default()
13856        },
13857        cx,
13858    )
13859    .await;
13860
13861    struct Run {
13862        run_description: &'static str,
13863        initial_state: String,
13864        buffer_marked_text: String,
13865        completion_label: &'static str,
13866        completion_text: &'static str,
13867        expected_with_insert_mode: String,
13868        expected_with_replace_mode: String,
13869        expected_with_replace_subsequence_mode: String,
13870        expected_with_replace_suffix_mode: String,
13871    }
13872
13873    let runs = [
13874        Run {
13875            run_description: "Start of word matches completion text",
13876            initial_state: "before ediˇ after".into(),
13877            buffer_marked_text: "before <edi|> after".into(),
13878            completion_label: "editor",
13879            completion_text: "editor",
13880            expected_with_insert_mode: "before editorˇ after".into(),
13881            expected_with_replace_mode: "before editorˇ after".into(),
13882            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13883            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13884        },
13885        Run {
13886            run_description: "Accept same text at the middle of the word",
13887            initial_state: "before ediˇtor after".into(),
13888            buffer_marked_text: "before <edi|tor> after".into(),
13889            completion_label: "editor",
13890            completion_text: "editor",
13891            expected_with_insert_mode: "before editorˇtor after".into(),
13892            expected_with_replace_mode: "before editorˇ after".into(),
13893            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13894            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13895        },
13896        Run {
13897            run_description: "End of word matches completion text -- cursor at end",
13898            initial_state: "before torˇ after".into(),
13899            buffer_marked_text: "before <tor|> after".into(),
13900            completion_label: "editor",
13901            completion_text: "editor",
13902            expected_with_insert_mode: "before editorˇ after".into(),
13903            expected_with_replace_mode: "before editorˇ after".into(),
13904            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13905            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13906        },
13907        Run {
13908            run_description: "End of word matches completion text -- cursor at start",
13909            initial_state: "before ˇtor after".into(),
13910            buffer_marked_text: "before <|tor> after".into(),
13911            completion_label: "editor",
13912            completion_text: "editor",
13913            expected_with_insert_mode: "before editorˇtor after".into(),
13914            expected_with_replace_mode: "before editorˇ after".into(),
13915            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
13916            expected_with_replace_suffix_mode: "before editorˇ after".into(),
13917        },
13918        Run {
13919            run_description: "Prepend text containing whitespace",
13920            initial_state: "pˇfield: bool".into(),
13921            buffer_marked_text: "<p|field>: bool".into(),
13922            completion_label: "pub ",
13923            completion_text: "pub ",
13924            expected_with_insert_mode: "pub ˇfield: bool".into(),
13925            expected_with_replace_mode: "pub ˇ: bool".into(),
13926            expected_with_replace_subsequence_mode: "pub ˇfield: bool".into(),
13927            expected_with_replace_suffix_mode: "pub ˇfield: bool".into(),
13928        },
13929        Run {
13930            run_description: "Add element to start of list",
13931            initial_state: "[element_ˇelement_2]".into(),
13932            buffer_marked_text: "[<element_|element_2>]".into(),
13933            completion_label: "element_1",
13934            completion_text: "element_1",
13935            expected_with_insert_mode: "[element_1ˇelement_2]".into(),
13936            expected_with_replace_mode: "[element_1ˇ]".into(),
13937            expected_with_replace_subsequence_mode: "[element_1ˇelement_2]".into(),
13938            expected_with_replace_suffix_mode: "[element_1ˇelement_2]".into(),
13939        },
13940        Run {
13941            run_description: "Add element to start of list -- first and second elements are equal",
13942            initial_state: "[elˇelement]".into(),
13943            buffer_marked_text: "[<el|element>]".into(),
13944            completion_label: "element",
13945            completion_text: "element",
13946            expected_with_insert_mode: "[elementˇelement]".into(),
13947            expected_with_replace_mode: "[elementˇ]".into(),
13948            expected_with_replace_subsequence_mode: "[elementˇelement]".into(),
13949            expected_with_replace_suffix_mode: "[elementˇ]".into(),
13950        },
13951        Run {
13952            run_description: "Ends with matching suffix",
13953            initial_state: "SubˇError".into(),
13954            buffer_marked_text: "<Sub|Error>".into(),
13955            completion_label: "SubscriptionError",
13956            completion_text: "SubscriptionError",
13957            expected_with_insert_mode: "SubscriptionErrorˇError".into(),
13958            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13959            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13960            expected_with_replace_suffix_mode: "SubscriptionErrorˇ".into(),
13961        },
13962        Run {
13963            run_description: "Suffix is a subsequence -- contiguous",
13964            initial_state: "SubˇErr".into(),
13965            buffer_marked_text: "<Sub|Err>".into(),
13966            completion_label: "SubscriptionError",
13967            completion_text: "SubscriptionError",
13968            expected_with_insert_mode: "SubscriptionErrorˇErr".into(),
13969            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13970            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13971            expected_with_replace_suffix_mode: "SubscriptionErrorˇErr".into(),
13972        },
13973        Run {
13974            run_description: "Suffix is a subsequence -- non-contiguous -- replace intended",
13975            initial_state: "Suˇscrirr".into(),
13976            buffer_marked_text: "<Su|scrirr>".into(),
13977            completion_label: "SubscriptionError",
13978            completion_text: "SubscriptionError",
13979            expected_with_insert_mode: "SubscriptionErrorˇscrirr".into(),
13980            expected_with_replace_mode: "SubscriptionErrorˇ".into(),
13981            expected_with_replace_subsequence_mode: "SubscriptionErrorˇ".into(),
13982            expected_with_replace_suffix_mode: "SubscriptionErrorˇscrirr".into(),
13983        },
13984        Run {
13985            run_description: "Suffix is a subsequence -- non-contiguous -- replace unintended",
13986            initial_state: "foo(indˇix)".into(),
13987            buffer_marked_text: "foo(<ind|ix>)".into(),
13988            completion_label: "node_index",
13989            completion_text: "node_index",
13990            expected_with_insert_mode: "foo(node_indexˇix)".into(),
13991            expected_with_replace_mode: "foo(node_indexˇ)".into(),
13992            expected_with_replace_subsequence_mode: "foo(node_indexˇix)".into(),
13993            expected_with_replace_suffix_mode: "foo(node_indexˇix)".into(),
13994        },
13995        Run {
13996            run_description: "Replace range ends before cursor - should extend to cursor",
13997            initial_state: "before editˇo after".into(),
13998            buffer_marked_text: "before <{ed}>it|o after".into(),
13999            completion_label: "editor",
14000            completion_text: "editor",
14001            expected_with_insert_mode: "before editorˇo after".into(),
14002            expected_with_replace_mode: "before editorˇo after".into(),
14003            expected_with_replace_subsequence_mode: "before editorˇo after".into(),
14004            expected_with_replace_suffix_mode: "before editorˇo after".into(),
14005        },
14006        Run {
14007            run_description: "Uses label for suffix matching",
14008            initial_state: "before ediˇtor after".into(),
14009            buffer_marked_text: "before <edi|tor> after".into(),
14010            completion_label: "editor",
14011            completion_text: "editor()",
14012            expected_with_insert_mode: "before editor()ˇtor after".into(),
14013            expected_with_replace_mode: "before editor()ˇ after".into(),
14014            expected_with_replace_subsequence_mode: "before editor()ˇ after".into(),
14015            expected_with_replace_suffix_mode: "before editor()ˇ after".into(),
14016        },
14017        Run {
14018            run_description: "Case insensitive subsequence and suffix matching",
14019            initial_state: "before EDiˇtoR after".into(),
14020            buffer_marked_text: "before <EDi|toR> after".into(),
14021            completion_label: "editor",
14022            completion_text: "editor",
14023            expected_with_insert_mode: "before editorˇtoR after".into(),
14024            expected_with_replace_mode: "before editorˇ after".into(),
14025            expected_with_replace_subsequence_mode: "before editorˇ after".into(),
14026            expected_with_replace_suffix_mode: "before editorˇ after".into(),
14027        },
14028    ];
14029
14030    for run in runs {
14031        let run_variations = [
14032            (LspInsertMode::Insert, run.expected_with_insert_mode),
14033            (LspInsertMode::Replace, run.expected_with_replace_mode),
14034            (
14035                LspInsertMode::ReplaceSubsequence,
14036                run.expected_with_replace_subsequence_mode,
14037            ),
14038            (
14039                LspInsertMode::ReplaceSuffix,
14040                run.expected_with_replace_suffix_mode,
14041            ),
14042        ];
14043
14044        for (lsp_insert_mode, expected_text) in run_variations {
14045            eprintln!(
14046                "run = {:?}, mode = {lsp_insert_mode:.?}",
14047                run.run_description,
14048            );
14049
14050            update_test_language_settings(&mut cx, |settings| {
14051                settings.defaults.completions = Some(CompletionSettingsContent {
14052                    lsp_insert_mode: Some(lsp_insert_mode),
14053                    words: Some(WordsCompletionMode::Disabled),
14054                    words_min_length: Some(0),
14055                    ..Default::default()
14056                });
14057            });
14058
14059            cx.set_state(&run.initial_state);
14060            cx.update_editor(|editor, window, cx| {
14061                editor.show_completions(&ShowCompletions, window, cx);
14062            });
14063
14064            let counter = Arc::new(AtomicUsize::new(0));
14065            handle_completion_request_with_insert_and_replace(
14066                &mut cx,
14067                &run.buffer_marked_text,
14068                vec![(run.completion_label, run.completion_text)],
14069                counter.clone(),
14070            )
14071            .await;
14072            cx.condition(|editor, _| editor.context_menu_visible())
14073                .await;
14074            assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14075
14076            let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14077                editor
14078                    .confirm_completion(&ConfirmCompletion::default(), window, cx)
14079                    .unwrap()
14080            });
14081            cx.assert_editor_state(&expected_text);
14082            handle_resolve_completion_request(&mut cx, None).await;
14083            apply_additional_edits.await.unwrap();
14084        }
14085    }
14086}
14087
14088#[gpui::test]
14089async fn test_completion_with_mode_specified_by_action(cx: &mut TestAppContext) {
14090    init_test(cx, |_| {});
14091    let mut cx = EditorLspTestContext::new_rust(
14092        lsp::ServerCapabilities {
14093            completion_provider: Some(lsp::CompletionOptions {
14094                resolve_provider: Some(true),
14095                ..Default::default()
14096            }),
14097            ..Default::default()
14098        },
14099        cx,
14100    )
14101    .await;
14102
14103    let initial_state = "SubˇError";
14104    let buffer_marked_text = "<Sub|Error>";
14105    let completion_text = "SubscriptionError";
14106    let expected_with_insert_mode = "SubscriptionErrorˇError";
14107    let expected_with_replace_mode = "SubscriptionErrorˇ";
14108
14109    update_test_language_settings(&mut cx, |settings| {
14110        settings.defaults.completions = Some(CompletionSettingsContent {
14111            words: Some(WordsCompletionMode::Disabled),
14112            words_min_length: Some(0),
14113            // set the opposite here to ensure that the action is overriding the default behavior
14114            lsp_insert_mode: Some(LspInsertMode::Insert),
14115            ..Default::default()
14116        });
14117    });
14118
14119    cx.set_state(initial_state);
14120    cx.update_editor(|editor, window, cx| {
14121        editor.show_completions(&ShowCompletions, window, cx);
14122    });
14123
14124    let counter = Arc::new(AtomicUsize::new(0));
14125    handle_completion_request_with_insert_and_replace(
14126        &mut cx,
14127        buffer_marked_text,
14128        vec![(completion_text, completion_text)],
14129        counter.clone(),
14130    )
14131    .await;
14132    cx.condition(|editor, _| editor.context_menu_visible())
14133        .await;
14134    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14135
14136    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14137        editor
14138            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14139            .unwrap()
14140    });
14141    cx.assert_editor_state(expected_with_replace_mode);
14142    handle_resolve_completion_request(&mut cx, None).await;
14143    apply_additional_edits.await.unwrap();
14144
14145    update_test_language_settings(&mut cx, |settings| {
14146        settings.defaults.completions = Some(CompletionSettingsContent {
14147            words: Some(WordsCompletionMode::Disabled),
14148            words_min_length: Some(0),
14149            // set the opposite here to ensure that the action is overriding the default behavior
14150            lsp_insert_mode: Some(LspInsertMode::Replace),
14151            ..Default::default()
14152        });
14153    });
14154
14155    cx.set_state(initial_state);
14156    cx.update_editor(|editor, window, cx| {
14157        editor.show_completions(&ShowCompletions, window, cx);
14158    });
14159    handle_completion_request_with_insert_and_replace(
14160        &mut cx,
14161        buffer_marked_text,
14162        vec![(completion_text, completion_text)],
14163        counter.clone(),
14164    )
14165    .await;
14166    cx.condition(|editor, _| editor.context_menu_visible())
14167        .await;
14168    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14169
14170    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14171        editor
14172            .confirm_completion_insert(&ConfirmCompletionInsert, window, cx)
14173            .unwrap()
14174    });
14175    cx.assert_editor_state(expected_with_insert_mode);
14176    handle_resolve_completion_request(&mut cx, None).await;
14177    apply_additional_edits.await.unwrap();
14178}
14179
14180#[gpui::test]
14181async fn test_completion_replacing_surrounding_text_with_multicursors(cx: &mut TestAppContext) {
14182    init_test(cx, |_| {});
14183    let mut cx = EditorLspTestContext::new_rust(
14184        lsp::ServerCapabilities {
14185            completion_provider: Some(lsp::CompletionOptions {
14186                resolve_provider: Some(true),
14187                ..Default::default()
14188            }),
14189            ..Default::default()
14190        },
14191        cx,
14192    )
14193    .await;
14194
14195    // scenario: surrounding text matches completion text
14196    let completion_text = "to_offset";
14197    let initial_state = indoc! {"
14198        1. buf.to_offˇsuffix
14199        2. buf.to_offˇsuf
14200        3. buf.to_offˇfix
14201        4. buf.to_offˇ
14202        5. into_offˇensive
14203        6. ˇsuffix
14204        7. let ˇ //
14205        8. aaˇzz
14206        9. buf.to_off«zzzzzˇ»suffix
14207        10. buf.«ˇzzzzz»suffix
14208        11. to_off«ˇzzzzz»
14209
14210        buf.to_offˇsuffix  // newest cursor
14211    "};
14212    let completion_marked_buffer = indoc! {"
14213        1. buf.to_offsuffix
14214        2. buf.to_offsuf
14215        3. buf.to_offfix
14216        4. buf.to_off
14217        5. into_offensive
14218        6. suffix
14219        7. let  //
14220        8. aazz
14221        9. buf.to_offzzzzzsuffix
14222        10. buf.zzzzzsuffix
14223        11. to_offzzzzz
14224
14225        buf.<to_off|suffix>  // newest cursor
14226    "};
14227    let expected = indoc! {"
14228        1. buf.to_offsetˇ
14229        2. buf.to_offsetˇsuf
14230        3. buf.to_offsetˇfix
14231        4. buf.to_offsetˇ
14232        5. into_offsetˇensive
14233        6. to_offsetˇsuffix
14234        7. let to_offsetˇ //
14235        8. aato_offsetˇzz
14236        9. buf.to_offsetˇ
14237        10. buf.to_offsetˇsuffix
14238        11. to_offsetˇ
14239
14240        buf.to_offsetˇ  // newest cursor
14241    "};
14242    cx.set_state(initial_state);
14243    cx.update_editor(|editor, window, cx| {
14244        editor.show_completions(&ShowCompletions, window, cx);
14245    });
14246    handle_completion_request_with_insert_and_replace(
14247        &mut cx,
14248        completion_marked_buffer,
14249        vec![(completion_text, completion_text)],
14250        Arc::new(AtomicUsize::new(0)),
14251    )
14252    .await;
14253    cx.condition(|editor, _| editor.context_menu_visible())
14254        .await;
14255    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14256        editor
14257            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14258            .unwrap()
14259    });
14260    cx.assert_editor_state(expected);
14261    handle_resolve_completion_request(&mut cx, None).await;
14262    apply_additional_edits.await.unwrap();
14263
14264    // scenario: surrounding text matches surroundings of newest cursor, inserting at the end
14265    let completion_text = "foo_and_bar";
14266    let initial_state = indoc! {"
14267        1. ooanbˇ
14268        2. zooanbˇ
14269        3. ooanbˇz
14270        4. zooanbˇz
14271        5. ooanˇ
14272        6. oanbˇ
14273
14274        ooanbˇ
14275    "};
14276    let completion_marked_buffer = indoc! {"
14277        1. ooanb
14278        2. zooanb
14279        3. ooanbz
14280        4. zooanbz
14281        5. ooan
14282        6. oanb
14283
14284        <ooanb|>
14285    "};
14286    let expected = indoc! {"
14287        1. foo_and_barˇ
14288        2. zfoo_and_barˇ
14289        3. foo_and_barˇz
14290        4. zfoo_and_barˇz
14291        5. ooanfoo_and_barˇ
14292        6. oanbfoo_and_barˇ
14293
14294        foo_and_barˇ
14295    "};
14296    cx.set_state(initial_state);
14297    cx.update_editor(|editor, window, cx| {
14298        editor.show_completions(&ShowCompletions, window, cx);
14299    });
14300    handle_completion_request_with_insert_and_replace(
14301        &mut cx,
14302        completion_marked_buffer,
14303        vec![(completion_text, completion_text)],
14304        Arc::new(AtomicUsize::new(0)),
14305    )
14306    .await;
14307    cx.condition(|editor, _| editor.context_menu_visible())
14308        .await;
14309    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14310        editor
14311            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14312            .unwrap()
14313    });
14314    cx.assert_editor_state(expected);
14315    handle_resolve_completion_request(&mut cx, None).await;
14316    apply_additional_edits.await.unwrap();
14317
14318    // scenario: surrounding text matches surroundings of newest cursor, inserted at the middle
14319    // (expects the same as if it was inserted at the end)
14320    let completion_text = "foo_and_bar";
14321    let initial_state = indoc! {"
14322        1. ooˇanb
14323        2. zooˇanb
14324        3. ooˇanbz
14325        4. zooˇanbz
14326
14327        ooˇanb
14328    "};
14329    let completion_marked_buffer = indoc! {"
14330        1. ooanb
14331        2. zooanb
14332        3. ooanbz
14333        4. zooanbz
14334
14335        <oo|anb>
14336    "};
14337    let expected = indoc! {"
14338        1. foo_and_barˇ
14339        2. zfoo_and_barˇ
14340        3. foo_and_barˇz
14341        4. zfoo_and_barˇz
14342
14343        foo_and_barˇ
14344    "};
14345    cx.set_state(initial_state);
14346    cx.update_editor(|editor, window, cx| {
14347        editor.show_completions(&ShowCompletions, window, cx);
14348    });
14349    handle_completion_request_with_insert_and_replace(
14350        &mut cx,
14351        completion_marked_buffer,
14352        vec![(completion_text, completion_text)],
14353        Arc::new(AtomicUsize::new(0)),
14354    )
14355    .await;
14356    cx.condition(|editor, _| editor.context_menu_visible())
14357        .await;
14358    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14359        editor
14360            .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14361            .unwrap()
14362    });
14363    cx.assert_editor_state(expected);
14364    handle_resolve_completion_request(&mut cx, None).await;
14365    apply_additional_edits.await.unwrap();
14366}
14367
14368// This used to crash
14369#[gpui::test]
14370async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppContext) {
14371    init_test(cx, |_| {});
14372
14373    let buffer_text = indoc! {"
14374        fn main() {
14375            10.satu;
14376
14377            //
14378            // separate cursors so they open in different excerpts (manually reproducible)
14379            //
14380
14381            10.satu20;
14382        }
14383    "};
14384    let multibuffer_text_with_selections = indoc! {"
14385        fn main() {
14386            10.satuˇ;
14387
14388            //
14389
14390            //
14391
14392            10.satuˇ20;
14393        }
14394    "};
14395    let expected_multibuffer = indoc! {"
14396        fn main() {
14397            10.saturating_sub()ˇ;
14398
14399            //
14400
14401            //
14402
14403            10.saturating_sub()ˇ;
14404        }
14405    "};
14406
14407    let first_excerpt_end = buffer_text.find("//").unwrap() + 3;
14408    let second_excerpt_end = buffer_text.rfind("//").unwrap() - 4;
14409
14410    let fs = FakeFs::new(cx.executor());
14411    fs.insert_tree(
14412        path!("/a"),
14413        json!({
14414            "main.rs": buffer_text,
14415        }),
14416    )
14417    .await;
14418
14419    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
14420    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
14421    language_registry.add(rust_lang());
14422    let mut fake_servers = language_registry.register_fake_lsp(
14423        "Rust",
14424        FakeLspAdapter {
14425            capabilities: lsp::ServerCapabilities {
14426                completion_provider: Some(lsp::CompletionOptions {
14427                    resolve_provider: None,
14428                    ..lsp::CompletionOptions::default()
14429                }),
14430                ..lsp::ServerCapabilities::default()
14431            },
14432            ..FakeLspAdapter::default()
14433        },
14434    );
14435    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
14436    let cx = &mut VisualTestContext::from_window(*workspace, cx);
14437    let buffer = project
14438        .update(cx, |project, cx| {
14439            project.open_local_buffer(path!("/a/main.rs"), cx)
14440        })
14441        .await
14442        .unwrap();
14443
14444    let multi_buffer = cx.new(|cx| {
14445        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
14446        multi_buffer.push_excerpts(
14447            buffer.clone(),
14448            [ExcerptRange::new(0..first_excerpt_end)],
14449            cx,
14450        );
14451        multi_buffer.push_excerpts(
14452            buffer.clone(),
14453            [ExcerptRange::new(second_excerpt_end..buffer_text.len())],
14454            cx,
14455        );
14456        multi_buffer
14457    });
14458
14459    let editor = workspace
14460        .update(cx, |_, window, cx| {
14461            cx.new(|cx| {
14462                Editor::new(
14463                    EditorMode::Full {
14464                        scale_ui_elements_with_buffer_font_size: false,
14465                        show_active_line_background: false,
14466                        sizing_behavior: SizingBehavior::Default,
14467                    },
14468                    multi_buffer.clone(),
14469                    Some(project.clone()),
14470                    window,
14471                    cx,
14472                )
14473            })
14474        })
14475        .unwrap();
14476
14477    let pane = workspace
14478        .update(cx, |workspace, _, _| workspace.active_pane().clone())
14479        .unwrap();
14480    pane.update_in(cx, |pane, window, cx| {
14481        pane.add_item(Box::new(editor.clone()), true, true, None, window, cx);
14482    });
14483
14484    let fake_server = fake_servers.next().await.unwrap();
14485
14486    editor.update_in(cx, |editor, window, cx| {
14487        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14488            s.select_ranges([
14489                Point::new(1, 11)..Point::new(1, 11),
14490                Point::new(7, 11)..Point::new(7, 11),
14491            ])
14492        });
14493
14494        assert_text_with_selections(editor, multibuffer_text_with_selections, cx);
14495    });
14496
14497    editor.update_in(cx, |editor, window, cx| {
14498        editor.show_completions(&ShowCompletions, window, cx);
14499    });
14500
14501    fake_server
14502        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
14503            let completion_item = lsp::CompletionItem {
14504                label: "saturating_sub()".into(),
14505                text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
14506                    lsp::InsertReplaceEdit {
14507                        new_text: "saturating_sub()".to_owned(),
14508                        insert: lsp::Range::new(
14509                            lsp::Position::new(7, 7),
14510                            lsp::Position::new(7, 11),
14511                        ),
14512                        replace: lsp::Range::new(
14513                            lsp::Position::new(7, 7),
14514                            lsp::Position::new(7, 13),
14515                        ),
14516                    },
14517                )),
14518                ..lsp::CompletionItem::default()
14519            };
14520
14521            Ok(Some(lsp::CompletionResponse::Array(vec![completion_item])))
14522        })
14523        .next()
14524        .await
14525        .unwrap();
14526
14527    cx.condition(&editor, |editor, _| editor.context_menu_visible())
14528        .await;
14529
14530    editor
14531        .update_in(cx, |editor, window, cx| {
14532            editor
14533                .confirm_completion_replace(&ConfirmCompletionReplace, window, cx)
14534                .unwrap()
14535        })
14536        .await
14537        .unwrap();
14538
14539    editor.update(cx, |editor, cx| {
14540        assert_text_with_selections(editor, expected_multibuffer, cx);
14541    })
14542}
14543
14544#[gpui::test]
14545async fn test_completion(cx: &mut TestAppContext) {
14546    init_test(cx, |_| {});
14547
14548    let mut cx = EditorLspTestContext::new_rust(
14549        lsp::ServerCapabilities {
14550            completion_provider: Some(lsp::CompletionOptions {
14551                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14552                resolve_provider: Some(true),
14553                ..Default::default()
14554            }),
14555            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14556            ..Default::default()
14557        },
14558        cx,
14559    )
14560    .await;
14561    let counter = Arc::new(AtomicUsize::new(0));
14562
14563    cx.set_state(indoc! {"
14564        oneˇ
14565        two
14566        three
14567    "});
14568    cx.simulate_keystroke(".");
14569    handle_completion_request(
14570        indoc! {"
14571            one.|<>
14572            two
14573            three
14574        "},
14575        vec!["first_completion", "second_completion"],
14576        true,
14577        counter.clone(),
14578        &mut cx,
14579    )
14580    .await;
14581    cx.condition(|editor, _| editor.context_menu_visible())
14582        .await;
14583    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14584
14585    let _handler = handle_signature_help_request(
14586        &mut cx,
14587        lsp::SignatureHelp {
14588            signatures: vec![lsp::SignatureInformation {
14589                label: "test signature".to_string(),
14590                documentation: None,
14591                parameters: Some(vec![lsp::ParameterInformation {
14592                    label: lsp::ParameterLabel::Simple("foo: u8".to_string()),
14593                    documentation: None,
14594                }]),
14595                active_parameter: None,
14596            }],
14597            active_signature: None,
14598            active_parameter: None,
14599        },
14600    );
14601    cx.update_editor(|editor, window, cx| {
14602        assert!(
14603            !editor.signature_help_state.is_shown(),
14604            "No signature help was called for"
14605        );
14606        editor.show_signature_help(&ShowSignatureHelp, window, cx);
14607    });
14608    cx.run_until_parked();
14609    cx.update_editor(|editor, _, _| {
14610        assert!(
14611            !editor.signature_help_state.is_shown(),
14612            "No signature help should be shown when completions menu is open"
14613        );
14614    });
14615
14616    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14617        editor.context_menu_next(&Default::default(), window, cx);
14618        editor
14619            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14620            .unwrap()
14621    });
14622    cx.assert_editor_state(indoc! {"
14623        one.second_completionˇ
14624        two
14625        three
14626    "});
14627
14628    handle_resolve_completion_request(
14629        &mut cx,
14630        Some(vec![
14631            (
14632                //This overlaps with the primary completion edit which is
14633                //misbehavior from the LSP spec, test that we filter it out
14634                indoc! {"
14635                    one.second_ˇcompletion
14636                    two
14637                    threeˇ
14638                "},
14639                "overlapping additional edit",
14640            ),
14641            (
14642                indoc! {"
14643                    one.second_completion
14644                    two
14645                    threeˇ
14646                "},
14647                "\nadditional edit",
14648            ),
14649        ]),
14650    )
14651    .await;
14652    apply_additional_edits.await.unwrap();
14653    cx.assert_editor_state(indoc! {"
14654        one.second_completionˇ
14655        two
14656        three
14657        additional edit
14658    "});
14659
14660    cx.set_state(indoc! {"
14661        one.second_completion
14662        twoˇ
14663        threeˇ
14664        additional edit
14665    "});
14666    cx.simulate_keystroke(" ");
14667    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14668    cx.simulate_keystroke("s");
14669    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14670
14671    cx.assert_editor_state(indoc! {"
14672        one.second_completion
14673        two sˇ
14674        three sˇ
14675        additional edit
14676    "});
14677    handle_completion_request(
14678        indoc! {"
14679            one.second_completion
14680            two s
14681            three <s|>
14682            additional edit
14683        "},
14684        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14685        true,
14686        counter.clone(),
14687        &mut cx,
14688    )
14689    .await;
14690    cx.condition(|editor, _| editor.context_menu_visible())
14691        .await;
14692    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14693
14694    cx.simulate_keystroke("i");
14695
14696    handle_completion_request(
14697        indoc! {"
14698            one.second_completion
14699            two si
14700            three <si|>
14701            additional edit
14702        "},
14703        vec!["fourth_completion", "fifth_completion", "sixth_completion"],
14704        true,
14705        counter.clone(),
14706        &mut cx,
14707    )
14708    .await;
14709    cx.condition(|editor, _| editor.context_menu_visible())
14710        .await;
14711    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14712
14713    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14714        editor
14715            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14716            .unwrap()
14717    });
14718    cx.assert_editor_state(indoc! {"
14719        one.second_completion
14720        two sixth_completionˇ
14721        three sixth_completionˇ
14722        additional edit
14723    "});
14724
14725    apply_additional_edits.await.unwrap();
14726
14727    update_test_language_settings(&mut cx, |settings| {
14728        settings.defaults.show_completions_on_input = Some(false);
14729    });
14730    cx.set_state("editorˇ");
14731    cx.simulate_keystroke(".");
14732    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14733    cx.simulate_keystrokes("c l o");
14734    cx.assert_editor_state("editor.cloˇ");
14735    assert!(cx.editor(|e, _, _| e.context_menu.borrow_mut().is_none()));
14736    cx.update_editor(|editor, window, cx| {
14737        editor.show_completions(&ShowCompletions, window, cx);
14738    });
14739    handle_completion_request(
14740        "editor.<clo|>",
14741        vec!["close", "clobber"],
14742        true,
14743        counter.clone(),
14744        &mut cx,
14745    )
14746    .await;
14747    cx.condition(|editor, _| editor.context_menu_visible())
14748        .await;
14749    assert_eq!(counter.load(atomic::Ordering::Acquire), 4);
14750
14751    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
14752        editor
14753            .confirm_completion(&ConfirmCompletion::default(), window, cx)
14754            .unwrap()
14755    });
14756    cx.assert_editor_state("editor.clobberˇ");
14757    handle_resolve_completion_request(&mut cx, None).await;
14758    apply_additional_edits.await.unwrap();
14759}
14760
14761#[gpui::test]
14762async fn test_completion_reuse(cx: &mut TestAppContext) {
14763    init_test(cx, |_| {});
14764
14765    let mut cx = EditorLspTestContext::new_rust(
14766        lsp::ServerCapabilities {
14767            completion_provider: Some(lsp::CompletionOptions {
14768                trigger_characters: Some(vec![".".to_string()]),
14769                ..Default::default()
14770            }),
14771            ..Default::default()
14772        },
14773        cx,
14774    )
14775    .await;
14776
14777    let counter = Arc::new(AtomicUsize::new(0));
14778    cx.set_state("objˇ");
14779    cx.simulate_keystroke(".");
14780
14781    // Initial completion request returns complete results
14782    let is_incomplete = false;
14783    handle_completion_request(
14784        "obj.|<>",
14785        vec!["a", "ab", "abc"],
14786        is_incomplete,
14787        counter.clone(),
14788        &mut cx,
14789    )
14790    .await;
14791    cx.run_until_parked();
14792    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14793    cx.assert_editor_state("obj.ˇ");
14794    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14795
14796    // Type "a" - filters existing completions
14797    cx.simulate_keystroke("a");
14798    cx.run_until_parked();
14799    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14800    cx.assert_editor_state("obj.aˇ");
14801    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14802
14803    // Type "b" - filters existing completions
14804    cx.simulate_keystroke("b");
14805    cx.run_until_parked();
14806    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14807    cx.assert_editor_state("obj.abˇ");
14808    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14809
14810    // Type "c" - filters existing completions
14811    cx.simulate_keystroke("c");
14812    cx.run_until_parked();
14813    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14814    cx.assert_editor_state("obj.abcˇ");
14815    check_displayed_completions(vec!["abc"], &mut cx);
14816
14817    // Backspace to delete "c" - filters existing completions
14818    cx.update_editor(|editor, window, cx| {
14819        editor.backspace(&Backspace, window, cx);
14820    });
14821    cx.run_until_parked();
14822    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14823    cx.assert_editor_state("obj.abˇ");
14824    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14825
14826    // Moving cursor to the left dismisses menu.
14827    cx.update_editor(|editor, window, cx| {
14828        editor.move_left(&MoveLeft, window, cx);
14829    });
14830    cx.run_until_parked();
14831    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
14832    cx.assert_editor_state("obj.aˇb");
14833    cx.update_editor(|editor, _, _| {
14834        assert_eq!(editor.context_menu_visible(), false);
14835    });
14836
14837    // Type "b" - new request
14838    cx.simulate_keystroke("b");
14839    let is_incomplete = false;
14840    handle_completion_request(
14841        "obj.<ab|>a",
14842        vec!["ab", "abc"],
14843        is_incomplete,
14844        counter.clone(),
14845        &mut cx,
14846    )
14847    .await;
14848    cx.run_until_parked();
14849    assert_eq!(counter.load(atomic::Ordering::Acquire), 2);
14850    cx.assert_editor_state("obj.abˇb");
14851    check_displayed_completions(vec!["ab", "abc"], &mut cx);
14852
14853    // Backspace to delete "b" - since query was "ab" and is now "a", new request is made.
14854    cx.update_editor(|editor, window, cx| {
14855        editor.backspace(&Backspace, window, cx);
14856    });
14857    let is_incomplete = false;
14858    handle_completion_request(
14859        "obj.<a|>b",
14860        vec!["a", "ab", "abc"],
14861        is_incomplete,
14862        counter.clone(),
14863        &mut cx,
14864    )
14865    .await;
14866    cx.run_until_parked();
14867    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14868    cx.assert_editor_state("obj.aˇb");
14869    check_displayed_completions(vec!["a", "ab", "abc"], &mut cx);
14870
14871    // Backspace to delete "a" - dismisses menu.
14872    cx.update_editor(|editor, window, cx| {
14873        editor.backspace(&Backspace, window, cx);
14874    });
14875    cx.run_until_parked();
14876    assert_eq!(counter.load(atomic::Ordering::Acquire), 3);
14877    cx.assert_editor_state("obj.ˇb");
14878    cx.update_editor(|editor, _, _| {
14879        assert_eq!(editor.context_menu_visible(), false);
14880    });
14881}
14882
14883#[gpui::test]
14884async fn test_word_completion(cx: &mut TestAppContext) {
14885    let lsp_fetch_timeout_ms = 10;
14886    init_test(cx, |language_settings| {
14887        language_settings.defaults.completions = Some(CompletionSettingsContent {
14888            words_min_length: Some(0),
14889            lsp_fetch_timeout_ms: Some(10),
14890            lsp_insert_mode: Some(LspInsertMode::Insert),
14891            ..Default::default()
14892        });
14893    });
14894
14895    let mut cx = EditorLspTestContext::new_rust(
14896        lsp::ServerCapabilities {
14897            completion_provider: Some(lsp::CompletionOptions {
14898                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14899                ..lsp::CompletionOptions::default()
14900            }),
14901            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14902            ..lsp::ServerCapabilities::default()
14903        },
14904        cx,
14905    )
14906    .await;
14907
14908    let throttle_completions = Arc::new(AtomicBool::new(false));
14909
14910    let lsp_throttle_completions = throttle_completions.clone();
14911    let _completion_requests_handler =
14912        cx.lsp
14913            .server
14914            .on_request::<lsp::request::Completion, _, _>(move |_, cx| {
14915                let lsp_throttle_completions = lsp_throttle_completions.clone();
14916                let cx = cx.clone();
14917                async move {
14918                    if lsp_throttle_completions.load(atomic::Ordering::Acquire) {
14919                        cx.background_executor()
14920                            .timer(Duration::from_millis(lsp_fetch_timeout_ms * 10))
14921                            .await;
14922                    }
14923                    Ok(Some(lsp::CompletionResponse::Array(vec![
14924                        lsp::CompletionItem {
14925                            label: "first".into(),
14926                            ..lsp::CompletionItem::default()
14927                        },
14928                        lsp::CompletionItem {
14929                            label: "last".into(),
14930                            ..lsp::CompletionItem::default()
14931                        },
14932                    ])))
14933                }
14934            });
14935
14936    cx.set_state(indoc! {"
14937        oneˇ
14938        two
14939        three
14940    "});
14941    cx.simulate_keystroke(".");
14942    cx.executor().run_until_parked();
14943    cx.condition(|editor, _| editor.context_menu_visible())
14944        .await;
14945    cx.update_editor(|editor, window, cx| {
14946        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14947        {
14948            assert_eq!(
14949                completion_menu_entries(menu),
14950                &["first", "last"],
14951                "When LSP server is fast to reply, no fallback word completions are used"
14952            );
14953        } else {
14954            panic!("expected completion menu to be open");
14955        }
14956        editor.cancel(&Cancel, window, cx);
14957    });
14958    cx.executor().run_until_parked();
14959    cx.condition(|editor, _| !editor.context_menu_visible())
14960        .await;
14961
14962    throttle_completions.store(true, atomic::Ordering::Release);
14963    cx.simulate_keystroke(".");
14964    cx.executor()
14965        .advance_clock(Duration::from_millis(lsp_fetch_timeout_ms * 2));
14966    cx.executor().run_until_parked();
14967    cx.condition(|editor, _| editor.context_menu_visible())
14968        .await;
14969    cx.update_editor(|editor, _, _| {
14970        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
14971        {
14972            assert_eq!(completion_menu_entries(menu), &["one", "three", "two"],
14973                "When LSP server is slow, document words can be shown instead, if configured accordingly");
14974        } else {
14975            panic!("expected completion menu to be open");
14976        }
14977    });
14978}
14979
14980#[gpui::test]
14981async fn test_word_completions_do_not_duplicate_lsp_ones(cx: &mut TestAppContext) {
14982    init_test(cx, |language_settings| {
14983        language_settings.defaults.completions = Some(CompletionSettingsContent {
14984            words: Some(WordsCompletionMode::Enabled),
14985            words_min_length: Some(0),
14986            lsp_insert_mode: Some(LspInsertMode::Insert),
14987            ..Default::default()
14988        });
14989    });
14990
14991    let mut cx = EditorLspTestContext::new_rust(
14992        lsp::ServerCapabilities {
14993            completion_provider: Some(lsp::CompletionOptions {
14994                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
14995                ..lsp::CompletionOptions::default()
14996            }),
14997            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
14998            ..lsp::ServerCapabilities::default()
14999        },
15000        cx,
15001    )
15002    .await;
15003
15004    let _completion_requests_handler =
15005        cx.lsp
15006            .server
15007            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15008                Ok(Some(lsp::CompletionResponse::Array(vec![
15009                    lsp::CompletionItem {
15010                        label: "first".into(),
15011                        ..lsp::CompletionItem::default()
15012                    },
15013                    lsp::CompletionItem {
15014                        label: "last".into(),
15015                        ..lsp::CompletionItem::default()
15016                    },
15017                ])))
15018            });
15019
15020    cx.set_state(indoc! {"ˇ
15021        first
15022        last
15023        second
15024    "});
15025    cx.simulate_keystroke(".");
15026    cx.executor().run_until_parked();
15027    cx.condition(|editor, _| editor.context_menu_visible())
15028        .await;
15029    cx.update_editor(|editor, _, _| {
15030        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15031        {
15032            assert_eq!(
15033                completion_menu_entries(menu),
15034                &["first", "last", "second"],
15035                "Word completions that has the same edit as the any of the LSP ones, should not be proposed"
15036            );
15037        } else {
15038            panic!("expected completion menu to be open");
15039        }
15040    });
15041}
15042
15043#[gpui::test]
15044async fn test_word_completions_continue_on_typing(cx: &mut TestAppContext) {
15045    init_test(cx, |language_settings| {
15046        language_settings.defaults.completions = Some(CompletionSettingsContent {
15047            words: Some(WordsCompletionMode::Disabled),
15048            words_min_length: Some(0),
15049            lsp_insert_mode: Some(LspInsertMode::Insert),
15050            ..Default::default()
15051        });
15052    });
15053
15054    let mut cx = EditorLspTestContext::new_rust(
15055        lsp::ServerCapabilities {
15056            completion_provider: Some(lsp::CompletionOptions {
15057                trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15058                ..lsp::CompletionOptions::default()
15059            }),
15060            signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15061            ..lsp::ServerCapabilities::default()
15062        },
15063        cx,
15064    )
15065    .await;
15066
15067    let _completion_requests_handler =
15068        cx.lsp
15069            .server
15070            .on_request::<lsp::request::Completion, _, _>(move |_, _| async move {
15071                panic!("LSP completions should not be queried when dealing with word completions")
15072            });
15073
15074    cx.set_state(indoc! {"ˇ
15075        first
15076        last
15077        second
15078    "});
15079    cx.update_editor(|editor, window, cx| {
15080        editor.show_word_completions(&ShowWordCompletions, window, cx);
15081    });
15082    cx.executor().run_until_parked();
15083    cx.condition(|editor, _| editor.context_menu_visible())
15084        .await;
15085    cx.update_editor(|editor, _, _| {
15086        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15087        {
15088            assert_eq!(
15089                completion_menu_entries(menu),
15090                &["first", "last", "second"],
15091                "`ShowWordCompletions` action should show word completions"
15092            );
15093        } else {
15094            panic!("expected completion menu to be open");
15095        }
15096    });
15097
15098    cx.simulate_keystroke("l");
15099    cx.executor().run_until_parked();
15100    cx.condition(|editor, _| editor.context_menu_visible())
15101        .await;
15102    cx.update_editor(|editor, _, _| {
15103        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15104        {
15105            assert_eq!(
15106                completion_menu_entries(menu),
15107                &["last"],
15108                "After showing word completions, further editing should filter them and not query the LSP"
15109            );
15110        } else {
15111            panic!("expected completion menu to be open");
15112        }
15113    });
15114}
15115
15116#[gpui::test]
15117async fn test_word_completions_usually_skip_digits(cx: &mut TestAppContext) {
15118    init_test(cx, |language_settings| {
15119        language_settings.defaults.completions = Some(CompletionSettingsContent {
15120            words_min_length: Some(0),
15121            lsp: Some(false),
15122            lsp_insert_mode: Some(LspInsertMode::Insert),
15123            ..Default::default()
15124        });
15125    });
15126
15127    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15128
15129    cx.set_state(indoc! {"ˇ
15130        0_usize
15131        let
15132        33
15133        4.5f32
15134    "});
15135    cx.update_editor(|editor, window, cx| {
15136        editor.show_completions(&ShowCompletions, window, cx);
15137    });
15138    cx.executor().run_until_parked();
15139    cx.condition(|editor, _| editor.context_menu_visible())
15140        .await;
15141    cx.update_editor(|editor, window, cx| {
15142        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15143        {
15144            assert_eq!(
15145                completion_menu_entries(menu),
15146                &["let"],
15147                "With no digits in the completion query, no digits should be in the word completions"
15148            );
15149        } else {
15150            panic!("expected completion menu to be open");
15151        }
15152        editor.cancel(&Cancel, window, cx);
15153    });
15154
15155    cx.set_state(indoc! {"15156        0_usize
15157        let
15158        3
15159        33.35f32
15160    "});
15161    cx.update_editor(|editor, window, cx| {
15162        editor.show_completions(&ShowCompletions, window, cx);
15163    });
15164    cx.executor().run_until_parked();
15165    cx.condition(|editor, _| editor.context_menu_visible())
15166        .await;
15167    cx.update_editor(|editor, _, _| {
15168        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15169        {
15170            assert_eq!(completion_menu_entries(menu), &["33", "35f32"], "The digit is in the completion query, \
15171                return matching words with digits (`33`, `35f32`) but exclude query duplicates (`3`)");
15172        } else {
15173            panic!("expected completion menu to be open");
15174        }
15175    });
15176}
15177
15178#[gpui::test]
15179async fn test_word_completions_do_not_show_before_threshold(cx: &mut TestAppContext) {
15180    init_test(cx, |language_settings| {
15181        language_settings.defaults.completions = Some(CompletionSettingsContent {
15182            words: Some(WordsCompletionMode::Enabled),
15183            words_min_length: Some(3),
15184            lsp_insert_mode: Some(LspInsertMode::Insert),
15185            ..Default::default()
15186        });
15187    });
15188
15189    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15190    cx.set_state(indoc! {"ˇ
15191        wow
15192        wowen
15193        wowser
15194    "});
15195    cx.simulate_keystroke("w");
15196    cx.executor().run_until_parked();
15197    cx.update_editor(|editor, _, _| {
15198        if editor.context_menu.borrow_mut().is_some() {
15199            panic!(
15200                "expected completion menu to be hidden, as words completion threshold is not met"
15201            );
15202        }
15203    });
15204
15205    cx.update_editor(|editor, window, cx| {
15206        editor.show_word_completions(&ShowWordCompletions, window, cx);
15207    });
15208    cx.executor().run_until_parked();
15209    cx.update_editor(|editor, window, cx| {
15210        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15211        {
15212            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");
15213        } else {
15214            panic!("expected completion menu to be open after the word completions are called with an action");
15215        }
15216
15217        editor.cancel(&Cancel, window, cx);
15218    });
15219    cx.update_editor(|editor, _, _| {
15220        if editor.context_menu.borrow_mut().is_some() {
15221            panic!("expected completion menu to be hidden after canceling");
15222        }
15223    });
15224
15225    cx.simulate_keystroke("o");
15226    cx.executor().run_until_parked();
15227    cx.update_editor(|editor, _, _| {
15228        if editor.context_menu.borrow_mut().is_some() {
15229            panic!(
15230                "expected completion menu to be hidden, as words completion threshold is not met still"
15231            );
15232        }
15233    });
15234
15235    cx.simulate_keystroke("w");
15236    cx.executor().run_until_parked();
15237    cx.update_editor(|editor, _, _| {
15238        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15239        {
15240            assert_eq!(completion_menu_entries(menu), &["wowen", "wowser"], "After word completion threshold is met, matching words should be shown, excluding the already typed word");
15241        } else {
15242            panic!("expected completion menu to be open after the word completions threshold is met");
15243        }
15244    });
15245}
15246
15247#[gpui::test]
15248async fn test_word_completions_disabled(cx: &mut TestAppContext) {
15249    init_test(cx, |language_settings| {
15250        language_settings.defaults.completions = Some(CompletionSettingsContent {
15251            words: Some(WordsCompletionMode::Enabled),
15252            words_min_length: Some(0),
15253            lsp_insert_mode: Some(LspInsertMode::Insert),
15254            ..Default::default()
15255        });
15256    });
15257
15258    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15259    cx.update_editor(|editor, _, _| {
15260        editor.disable_word_completions();
15261    });
15262    cx.set_state(indoc! {"ˇ
15263        wow
15264        wowen
15265        wowser
15266    "});
15267    cx.simulate_keystroke("w");
15268    cx.executor().run_until_parked();
15269    cx.update_editor(|editor, _, _| {
15270        if editor.context_menu.borrow_mut().is_some() {
15271            panic!(
15272                "expected completion menu to be hidden, as words completion are disabled for this editor"
15273            );
15274        }
15275    });
15276
15277    cx.update_editor(|editor, window, cx| {
15278        editor.show_word_completions(&ShowWordCompletions, window, cx);
15279    });
15280    cx.executor().run_until_parked();
15281    cx.update_editor(|editor, _, _| {
15282        if editor.context_menu.borrow_mut().is_some() {
15283            panic!(
15284                "expected completion menu to be hidden even if called for explicitly, as words completion are disabled for this editor"
15285            );
15286        }
15287    });
15288}
15289
15290#[gpui::test]
15291async fn test_word_completions_disabled_with_no_provider(cx: &mut TestAppContext) {
15292    init_test(cx, |language_settings| {
15293        language_settings.defaults.completions = Some(CompletionSettingsContent {
15294            words: Some(WordsCompletionMode::Disabled),
15295            words_min_length: Some(0),
15296            lsp_insert_mode: Some(LspInsertMode::Insert),
15297            ..Default::default()
15298        });
15299    });
15300
15301    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
15302    cx.update_editor(|editor, _, _| {
15303        editor.set_completion_provider(None);
15304    });
15305    cx.set_state(indoc! {"ˇ
15306        wow
15307        wowen
15308        wowser
15309    "});
15310    cx.simulate_keystroke("w");
15311    cx.executor().run_until_parked();
15312    cx.update_editor(|editor, _, _| {
15313        if editor.context_menu.borrow_mut().is_some() {
15314            panic!("expected completion menu to be hidden, as disabled in settings");
15315        }
15316    });
15317}
15318
15319fn gen_text_edit(params: &CompletionParams, text: &str) -> Option<lsp::CompletionTextEdit> {
15320    let position = || lsp::Position {
15321        line: params.text_document_position.position.line,
15322        character: params.text_document_position.position.character,
15323    };
15324    Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15325        range: lsp::Range {
15326            start: position(),
15327            end: position(),
15328        },
15329        new_text: text.to_string(),
15330    }))
15331}
15332
15333#[gpui::test]
15334async fn test_multiline_completion(cx: &mut TestAppContext) {
15335    init_test(cx, |_| {});
15336
15337    let fs = FakeFs::new(cx.executor());
15338    fs.insert_tree(
15339        path!("/a"),
15340        json!({
15341            "main.ts": "a",
15342        }),
15343    )
15344    .await;
15345
15346    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
15347    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
15348    let typescript_language = Arc::new(Language::new(
15349        LanguageConfig {
15350            name: "TypeScript".into(),
15351            matcher: LanguageMatcher {
15352                path_suffixes: vec!["ts".to_string()],
15353                ..LanguageMatcher::default()
15354            },
15355            line_comments: vec!["// ".into()],
15356            ..LanguageConfig::default()
15357        },
15358        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
15359    ));
15360    language_registry.add(typescript_language.clone());
15361    let mut fake_servers = language_registry.register_fake_lsp(
15362        "TypeScript",
15363        FakeLspAdapter {
15364            capabilities: lsp::ServerCapabilities {
15365                completion_provider: Some(lsp::CompletionOptions {
15366                    trigger_characters: Some(vec![".".to_string(), ":".to_string()]),
15367                    ..lsp::CompletionOptions::default()
15368                }),
15369                signature_help_provider: Some(lsp::SignatureHelpOptions::default()),
15370                ..lsp::ServerCapabilities::default()
15371            },
15372            // Emulate vtsls label generation
15373            label_for_completion: Some(Box::new(|item, _| {
15374                let text = if let Some(description) = item
15375                    .label_details
15376                    .as_ref()
15377                    .and_then(|label_details| label_details.description.as_ref())
15378                {
15379                    format!("{} {}", item.label, description)
15380                } else if let Some(detail) = &item.detail {
15381                    format!("{} {}", item.label, detail)
15382                } else {
15383                    item.label.clone()
15384                };
15385                Some(language::CodeLabel::plain(text, None))
15386            })),
15387            ..FakeLspAdapter::default()
15388        },
15389    );
15390    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
15391    let cx = &mut VisualTestContext::from_window(*workspace, cx);
15392    let worktree_id = workspace
15393        .update(cx, |workspace, _window, cx| {
15394            workspace.project().update(cx, |project, cx| {
15395                project.worktrees(cx).next().unwrap().read(cx).id()
15396            })
15397        })
15398        .unwrap();
15399    let _buffer = project
15400        .update(cx, |project, cx| {
15401            project.open_local_buffer_with_lsp(path!("/a/main.ts"), cx)
15402        })
15403        .await
15404        .unwrap();
15405    let editor = workspace
15406        .update(cx, |workspace, window, cx| {
15407            workspace.open_path((worktree_id, rel_path("main.ts")), None, true, window, cx)
15408        })
15409        .unwrap()
15410        .await
15411        .unwrap()
15412        .downcast::<Editor>()
15413        .unwrap();
15414    let fake_server = fake_servers.next().await.unwrap();
15415
15416    let multiline_label = "StickyHeaderExcerpt {\n            excerpt,\n            next_excerpt_controls_present,\n            next_buffer_row,\n        }: StickyHeaderExcerpt<'_>,";
15417    let multiline_label_2 = "a\nb\nc\n";
15418    let multiline_detail = "[]struct {\n\tSignerId\tstruct {\n\t\tIssuer\t\t\tstring\t`json:\"issuer\"`\n\t\tSubjectSerialNumber\"`\n}}";
15419    let multiline_description = "d\ne\nf\n";
15420    let multiline_detail_2 = "g\nh\ni\n";
15421
15422    let mut completion_handle = fake_server.set_request_handler::<lsp::request::Completion, _, _>(
15423        move |params, _| async move {
15424            Ok(Some(lsp::CompletionResponse::Array(vec![
15425                lsp::CompletionItem {
15426                    label: multiline_label.to_string(),
15427                    text_edit: gen_text_edit(&params, "new_text_1"),
15428                    ..lsp::CompletionItem::default()
15429                },
15430                lsp::CompletionItem {
15431                    label: "single line label 1".to_string(),
15432                    detail: Some(multiline_detail.to_string()),
15433                    text_edit: gen_text_edit(&params, "new_text_2"),
15434                    ..lsp::CompletionItem::default()
15435                },
15436                lsp::CompletionItem {
15437                    label: "single line label 2".to_string(),
15438                    label_details: Some(lsp::CompletionItemLabelDetails {
15439                        description: Some(multiline_description.to_string()),
15440                        detail: None,
15441                    }),
15442                    text_edit: gen_text_edit(&params, "new_text_2"),
15443                    ..lsp::CompletionItem::default()
15444                },
15445                lsp::CompletionItem {
15446                    label: multiline_label_2.to_string(),
15447                    detail: Some(multiline_detail_2.to_string()),
15448                    text_edit: gen_text_edit(&params, "new_text_3"),
15449                    ..lsp::CompletionItem::default()
15450                },
15451                lsp::CompletionItem {
15452                    label: "Label with many     spaces and \t but without newlines".to_string(),
15453                    detail: Some(
15454                        "Details with many     spaces and \t but without newlines".to_string(),
15455                    ),
15456                    text_edit: gen_text_edit(&params, "new_text_4"),
15457                    ..lsp::CompletionItem::default()
15458                },
15459            ])))
15460        },
15461    );
15462
15463    editor.update_in(cx, |editor, window, cx| {
15464        cx.focus_self(window);
15465        editor.move_to_end(&MoveToEnd, window, cx);
15466        editor.handle_input(".", window, cx);
15467    });
15468    cx.run_until_parked();
15469    completion_handle.next().await.unwrap();
15470
15471    editor.update(cx, |editor, _| {
15472        assert!(editor.context_menu_visible());
15473        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15474        {
15475            let completion_labels = menu
15476                .completions
15477                .borrow()
15478                .iter()
15479                .map(|c| c.label.text.clone())
15480                .collect::<Vec<_>>();
15481            assert_eq!(
15482                completion_labels,
15483                &[
15484                    "StickyHeaderExcerpt { excerpt, next_excerpt_controls_present, next_buffer_row, }: StickyHeaderExcerpt<'_>,",
15485                    "single line label 1 []struct { SignerId struct { Issuer string `json:\"issuer\"` SubjectSerialNumber\"` }}",
15486                    "single line label 2 d e f ",
15487                    "a b c g h i ",
15488                    "Label with many     spaces and \t but without newlines Details with many     spaces and \t but without newlines",
15489                ],
15490                "Completion items should have their labels without newlines, also replacing excessive whitespaces. Completion items without newlines should not be altered.",
15491            );
15492
15493            for completion in menu
15494                .completions
15495                .borrow()
15496                .iter() {
15497                    assert_eq!(
15498                        completion.label.filter_range,
15499                        0..completion.label.text.len(),
15500                        "Adjusted completion items should still keep their filter ranges for the entire label. Item: {completion:?}"
15501                    );
15502                }
15503        } else {
15504            panic!("expected completion menu to be open");
15505        }
15506    });
15507}
15508
15509#[gpui::test]
15510async fn test_completion_page_up_down_keys(cx: &mut TestAppContext) {
15511    init_test(cx, |_| {});
15512    let mut cx = EditorLspTestContext::new_rust(
15513        lsp::ServerCapabilities {
15514            completion_provider: Some(lsp::CompletionOptions {
15515                trigger_characters: Some(vec![".".to_string()]),
15516                ..Default::default()
15517            }),
15518            ..Default::default()
15519        },
15520        cx,
15521    )
15522    .await;
15523    cx.lsp
15524        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15525            Ok(Some(lsp::CompletionResponse::Array(vec![
15526                lsp::CompletionItem {
15527                    label: "first".into(),
15528                    ..Default::default()
15529                },
15530                lsp::CompletionItem {
15531                    label: "last".into(),
15532                    ..Default::default()
15533                },
15534            ])))
15535        });
15536    cx.set_state("variableˇ");
15537    cx.simulate_keystroke(".");
15538    cx.executor().run_until_parked();
15539
15540    cx.update_editor(|editor, _, _| {
15541        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15542        {
15543            assert_eq!(completion_menu_entries(menu), &["first", "last"]);
15544        } else {
15545            panic!("expected completion menu to be open");
15546        }
15547    });
15548
15549    cx.update_editor(|editor, window, cx| {
15550        editor.move_page_down(&MovePageDown::default(), window, cx);
15551        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15552        {
15553            assert!(
15554                menu.selected_item == 1,
15555                "expected PageDown to select the last item from the context menu"
15556            );
15557        } else {
15558            panic!("expected completion menu to stay open after PageDown");
15559        }
15560    });
15561
15562    cx.update_editor(|editor, window, cx| {
15563        editor.move_page_up(&MovePageUp::default(), window, cx);
15564        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
15565        {
15566            assert!(
15567                menu.selected_item == 0,
15568                "expected PageUp to select the first item from the context menu"
15569            );
15570        } else {
15571            panic!("expected completion menu to stay open after PageUp");
15572        }
15573    });
15574}
15575
15576#[gpui::test]
15577async fn test_as_is_completions(cx: &mut TestAppContext) {
15578    init_test(cx, |_| {});
15579    let mut cx = EditorLspTestContext::new_rust(
15580        lsp::ServerCapabilities {
15581            completion_provider: Some(lsp::CompletionOptions {
15582                ..Default::default()
15583            }),
15584            ..Default::default()
15585        },
15586        cx,
15587    )
15588    .await;
15589    cx.lsp
15590        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15591            Ok(Some(lsp::CompletionResponse::Array(vec![
15592                lsp::CompletionItem {
15593                    label: "unsafe".into(),
15594                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15595                        range: lsp::Range {
15596                            start: lsp::Position {
15597                                line: 1,
15598                                character: 2,
15599                            },
15600                            end: lsp::Position {
15601                                line: 1,
15602                                character: 3,
15603                            },
15604                        },
15605                        new_text: "unsafe".to_string(),
15606                    })),
15607                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
15608                    ..Default::default()
15609                },
15610            ])))
15611        });
15612    cx.set_state("fn a() {}\n");
15613    cx.executor().run_until_parked();
15614    cx.update_editor(|editor, window, cx| {
15615        editor.trigger_completion_on_input("n", true, window, cx)
15616    });
15617    cx.executor().run_until_parked();
15618
15619    cx.update_editor(|editor, window, cx| {
15620        editor.confirm_completion(&Default::default(), window, cx)
15621    });
15622    cx.executor().run_until_parked();
15623    cx.assert_editor_state("fn a() {}\n  unsafeˇ");
15624}
15625
15626#[gpui::test]
15627async fn test_panic_during_c_completions(cx: &mut TestAppContext) {
15628    init_test(cx, |_| {});
15629    let language =
15630        Arc::try_unwrap(languages::language("c", tree_sitter_c::LANGUAGE.into())).unwrap();
15631    let mut cx = EditorLspTestContext::new(
15632        language,
15633        lsp::ServerCapabilities {
15634            completion_provider: Some(lsp::CompletionOptions {
15635                ..lsp::CompletionOptions::default()
15636            }),
15637            ..lsp::ServerCapabilities::default()
15638        },
15639        cx,
15640    )
15641    .await;
15642
15643    cx.set_state(
15644        "#ifndef BAR_H
15645#define BAR_H
15646
15647#include <stdbool.h>
15648
15649int fn_branch(bool do_branch1, bool do_branch2);
15650
15651#endif // BAR_H
15652ˇ",
15653    );
15654    cx.executor().run_until_parked();
15655    cx.update_editor(|editor, window, cx| {
15656        editor.handle_input("#", window, cx);
15657    });
15658    cx.executor().run_until_parked();
15659    cx.update_editor(|editor, window, cx| {
15660        editor.handle_input("i", window, cx);
15661    });
15662    cx.executor().run_until_parked();
15663    cx.update_editor(|editor, window, cx| {
15664        editor.handle_input("n", window, cx);
15665    });
15666    cx.executor().run_until_parked();
15667    cx.assert_editor_state(
15668        "#ifndef BAR_H
15669#define BAR_H
15670
15671#include <stdbool.h>
15672
15673int fn_branch(bool do_branch1, bool do_branch2);
15674
15675#endif // BAR_H
15676#inˇ",
15677    );
15678
15679    cx.lsp
15680        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15681            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15682                is_incomplete: false,
15683                item_defaults: None,
15684                items: vec![lsp::CompletionItem {
15685                    kind: Some(lsp::CompletionItemKind::SNIPPET),
15686                    label_details: Some(lsp::CompletionItemLabelDetails {
15687                        detail: Some("header".to_string()),
15688                        description: None,
15689                    }),
15690                    label: " include".to_string(),
15691                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15692                        range: lsp::Range {
15693                            start: lsp::Position {
15694                                line: 8,
15695                                character: 1,
15696                            },
15697                            end: lsp::Position {
15698                                line: 8,
15699                                character: 1,
15700                            },
15701                        },
15702                        new_text: "include \"$0\"".to_string(),
15703                    })),
15704                    sort_text: Some("40b67681include".to_string()),
15705                    insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15706                    filter_text: Some("include".to_string()),
15707                    insert_text: Some("include \"$0\"".to_string()),
15708                    ..lsp::CompletionItem::default()
15709                }],
15710            })))
15711        });
15712    cx.update_editor(|editor, window, cx| {
15713        editor.show_completions(&ShowCompletions, window, cx);
15714    });
15715    cx.executor().run_until_parked();
15716    cx.update_editor(|editor, window, cx| {
15717        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15718    });
15719    cx.executor().run_until_parked();
15720    cx.assert_editor_state(
15721        "#ifndef BAR_H
15722#define BAR_H
15723
15724#include <stdbool.h>
15725
15726int fn_branch(bool do_branch1, bool do_branch2);
15727
15728#endif // BAR_H
15729#include \"ˇ\"",
15730    );
15731
15732    cx.lsp
15733        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
15734            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15735                is_incomplete: true,
15736                item_defaults: None,
15737                items: vec![lsp::CompletionItem {
15738                    kind: Some(lsp::CompletionItemKind::FILE),
15739                    label: "AGL/".to_string(),
15740                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15741                        range: lsp::Range {
15742                            start: lsp::Position {
15743                                line: 8,
15744                                character: 10,
15745                            },
15746                            end: lsp::Position {
15747                                line: 8,
15748                                character: 11,
15749                            },
15750                        },
15751                        new_text: "AGL/".to_string(),
15752                    })),
15753                    sort_text: Some("40b67681AGL/".to_string()),
15754                    insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
15755                    filter_text: Some("AGL/".to_string()),
15756                    insert_text: Some("AGL/".to_string()),
15757                    ..lsp::CompletionItem::default()
15758                }],
15759            })))
15760        });
15761    cx.update_editor(|editor, window, cx| {
15762        editor.show_completions(&ShowCompletions, window, cx);
15763    });
15764    cx.executor().run_until_parked();
15765    cx.update_editor(|editor, window, cx| {
15766        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
15767    });
15768    cx.executor().run_until_parked();
15769    cx.assert_editor_state(
15770        r##"#ifndef BAR_H
15771#define BAR_H
15772
15773#include <stdbool.h>
15774
15775int fn_branch(bool do_branch1, bool do_branch2);
15776
15777#endif // BAR_H
15778#include "AGL/ˇ"##,
15779    );
15780
15781    cx.update_editor(|editor, window, cx| {
15782        editor.handle_input("\"", window, cx);
15783    });
15784    cx.executor().run_until_parked();
15785    cx.assert_editor_state(
15786        r##"#ifndef BAR_H
15787#define BAR_H
15788
15789#include <stdbool.h>
15790
15791int fn_branch(bool do_branch1, bool do_branch2);
15792
15793#endif // BAR_H
15794#include "AGL/"ˇ"##,
15795    );
15796}
15797
15798#[gpui::test]
15799async fn test_no_duplicated_completion_requests(cx: &mut TestAppContext) {
15800    init_test(cx, |_| {});
15801
15802    let mut cx = EditorLspTestContext::new_rust(
15803        lsp::ServerCapabilities {
15804            completion_provider: Some(lsp::CompletionOptions {
15805                trigger_characters: Some(vec![".".to_string()]),
15806                resolve_provider: Some(true),
15807                ..Default::default()
15808            }),
15809            ..Default::default()
15810        },
15811        cx,
15812    )
15813    .await;
15814
15815    cx.set_state("fn main() { let a = 2ˇ; }");
15816    cx.simulate_keystroke(".");
15817    let completion_item = lsp::CompletionItem {
15818        label: "Some".into(),
15819        kind: Some(lsp::CompletionItemKind::SNIPPET),
15820        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
15821        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
15822            kind: lsp::MarkupKind::Markdown,
15823            value: "```rust\nSome(2)\n```".to_string(),
15824        })),
15825        deprecated: Some(false),
15826        sort_text: Some("Some".to_string()),
15827        filter_text: Some("Some".to_string()),
15828        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
15829        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
15830            range: lsp::Range {
15831                start: lsp::Position {
15832                    line: 0,
15833                    character: 22,
15834                },
15835                end: lsp::Position {
15836                    line: 0,
15837                    character: 22,
15838                },
15839            },
15840            new_text: "Some(2)".to_string(),
15841        })),
15842        additional_text_edits: Some(vec![lsp::TextEdit {
15843            range: lsp::Range {
15844                start: lsp::Position {
15845                    line: 0,
15846                    character: 20,
15847                },
15848                end: lsp::Position {
15849                    line: 0,
15850                    character: 22,
15851                },
15852            },
15853            new_text: "".to_string(),
15854        }]),
15855        ..Default::default()
15856    };
15857
15858    let closure_completion_item = completion_item.clone();
15859    let counter = Arc::new(AtomicUsize::new(0));
15860    let counter_clone = counter.clone();
15861    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
15862        let task_completion_item = closure_completion_item.clone();
15863        counter_clone.fetch_add(1, atomic::Ordering::Release);
15864        async move {
15865            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
15866                is_incomplete: true,
15867                item_defaults: None,
15868                items: vec![task_completion_item],
15869            })))
15870        }
15871    });
15872
15873    cx.condition(|editor, _| editor.context_menu_visible())
15874        .await;
15875    cx.assert_editor_state("fn main() { let a = 2.ˇ; }");
15876    assert!(request.next().await.is_some());
15877    assert_eq!(counter.load(atomic::Ordering::Acquire), 1);
15878
15879    cx.simulate_keystrokes("S o m");
15880    cx.condition(|editor, _| editor.context_menu_visible())
15881        .await;
15882    cx.assert_editor_state("fn main() { let a = 2.Somˇ; }");
15883    assert!(request.next().await.is_some());
15884    assert!(request.next().await.is_some());
15885    assert!(request.next().await.is_some());
15886    request.close();
15887    assert!(request.next().await.is_none());
15888    assert_eq!(
15889        counter.load(atomic::Ordering::Acquire),
15890        4,
15891        "With the completions menu open, only one LSP request should happen per input"
15892    );
15893}
15894
15895#[gpui::test]
15896async fn test_toggle_comment(cx: &mut TestAppContext) {
15897    init_test(cx, |_| {});
15898    let mut cx = EditorTestContext::new(cx).await;
15899    let language = Arc::new(Language::new(
15900        LanguageConfig {
15901            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
15902            ..Default::default()
15903        },
15904        Some(tree_sitter_rust::LANGUAGE.into()),
15905    ));
15906    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
15907
15908    // If multiple selections intersect a line, the line is only toggled once.
15909    cx.set_state(indoc! {"
15910        fn a() {
15911            «//b();
15912            ˇ»// «c();
15913            //ˇ»  d();
15914        }
15915    "});
15916
15917    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15918
15919    cx.assert_editor_state(indoc! {"
15920        fn a() {
15921            «b();
15922            c();
15923            ˇ» d();
15924        }
15925    "});
15926
15927    // The comment prefix is inserted at the same column for every line in a
15928    // selection.
15929    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15930
15931    cx.assert_editor_state(indoc! {"
15932        fn a() {
15933            // «b();
15934            // c();
15935            ˇ»//  d();
15936        }
15937    "});
15938
15939    // If a selection ends at the beginning of a line, that line is not toggled.
15940    cx.set_selections_state(indoc! {"
15941        fn a() {
15942            // b();
15943            «// c();
15944        ˇ»    //  d();
15945        }
15946    "});
15947
15948    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15949
15950    cx.assert_editor_state(indoc! {"
15951        fn a() {
15952            // b();
15953            «c();
15954        ˇ»    //  d();
15955        }
15956    "});
15957
15958    // If a selection span a single line and is empty, the line is toggled.
15959    cx.set_state(indoc! {"
15960        fn a() {
15961            a();
15962            b();
15963        ˇ
15964        }
15965    "});
15966
15967    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15968
15969    cx.assert_editor_state(indoc! {"
15970        fn a() {
15971            a();
15972            b();
15973        //•ˇ
15974        }
15975    "});
15976
15977    // If a selection span multiple lines, empty lines are not toggled.
15978    cx.set_state(indoc! {"
15979        fn a() {
15980            «a();
15981
15982            c();ˇ»
15983        }
15984    "});
15985
15986    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
15987
15988    cx.assert_editor_state(indoc! {"
15989        fn a() {
15990            // «a();
15991
15992            // c();ˇ»
15993        }
15994    "});
15995
15996    // If a selection includes multiple comment prefixes, all lines are uncommented.
15997    cx.set_state(indoc! {"
15998        fn a() {
15999            «// a();
16000            /// b();
16001            //! c();ˇ»
16002        }
16003    "});
16004
16005    cx.update_editor(|e, window, cx| e.toggle_comments(&ToggleComments::default(), window, cx));
16006
16007    cx.assert_editor_state(indoc! {"
16008        fn a() {
16009            «a();
16010            b();
16011            c();ˇ»
16012        }
16013    "});
16014}
16015
16016#[gpui::test]
16017async fn test_toggle_comment_ignore_indent(cx: &mut TestAppContext) {
16018    init_test(cx, |_| {});
16019    let mut cx = EditorTestContext::new(cx).await;
16020    let language = Arc::new(Language::new(
16021        LanguageConfig {
16022            line_comments: vec!["// ".into(), "//! ".into(), "/// ".into()],
16023            ..Default::default()
16024        },
16025        Some(tree_sitter_rust::LANGUAGE.into()),
16026    ));
16027    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
16028
16029    let toggle_comments = &ToggleComments {
16030        advance_downwards: false,
16031        ignore_indent: true,
16032    };
16033
16034    // If multiple selections intersect a line, the line is only toggled once.
16035    cx.set_state(indoc! {"
16036        fn a() {
16037        //    «b();
16038        //    c();
16039        //    ˇ» d();
16040        }
16041    "});
16042
16043    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16044
16045    cx.assert_editor_state(indoc! {"
16046        fn a() {
16047            «b();
16048            c();
16049            ˇ» d();
16050        }
16051    "});
16052
16053    // The comment prefix is inserted at the beginning of each line
16054    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16055
16056    cx.assert_editor_state(indoc! {"
16057        fn a() {
16058        //    «b();
16059        //    c();
16060        //    ˇ» d();
16061        }
16062    "});
16063
16064    // If a selection ends at the beginning of a line, that line is not toggled.
16065    cx.set_selections_state(indoc! {"
16066        fn a() {
16067        //    b();
16068        //    «c();
16069        ˇ»//     d();
16070        }
16071    "});
16072
16073    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16074
16075    cx.assert_editor_state(indoc! {"
16076        fn a() {
16077        //    b();
16078            «c();
16079        ˇ»//     d();
16080        }
16081    "});
16082
16083    // If a selection span a single line and is empty, the line is toggled.
16084    cx.set_state(indoc! {"
16085        fn a() {
16086            a();
16087            b();
16088        ˇ
16089        }
16090    "});
16091
16092    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16093
16094    cx.assert_editor_state(indoc! {"
16095        fn a() {
16096            a();
16097            b();
16098        //ˇ
16099        }
16100    "});
16101
16102    // If a selection span multiple lines, empty lines are not toggled.
16103    cx.set_state(indoc! {"
16104        fn a() {
16105            «a();
16106
16107            c();ˇ»
16108        }
16109    "});
16110
16111    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16112
16113    cx.assert_editor_state(indoc! {"
16114        fn a() {
16115        //    «a();
16116
16117        //    c();ˇ»
16118        }
16119    "});
16120
16121    // If a selection includes multiple comment prefixes, all lines are uncommented.
16122    cx.set_state(indoc! {"
16123        fn a() {
16124        //    «a();
16125        ///    b();
16126        //!    c();ˇ»
16127        }
16128    "});
16129
16130    cx.update_editor(|e, window, cx| e.toggle_comments(toggle_comments, window, cx));
16131
16132    cx.assert_editor_state(indoc! {"
16133        fn a() {
16134            «a();
16135            b();
16136            c();ˇ»
16137        }
16138    "});
16139}
16140
16141#[gpui::test]
16142async fn test_advance_downward_on_toggle_comment(cx: &mut TestAppContext) {
16143    init_test(cx, |_| {});
16144
16145    let language = Arc::new(Language::new(
16146        LanguageConfig {
16147            line_comments: vec!["// ".into()],
16148            ..Default::default()
16149        },
16150        Some(tree_sitter_rust::LANGUAGE.into()),
16151    ));
16152
16153    let mut cx = EditorTestContext::new(cx).await;
16154
16155    cx.language_registry().add(language.clone());
16156    cx.update_buffer(|buffer, cx| {
16157        buffer.set_language_immediate(Some(language), cx);
16158    });
16159
16160    let toggle_comments = &ToggleComments {
16161        advance_downwards: true,
16162        ignore_indent: false,
16163    };
16164
16165    // Single cursor on one line -> advance
16166    // Cursor moves horizontally 3 characters as well on non-blank line
16167    cx.set_state(indoc!(
16168        "fn a() {
16169             ˇdog();
16170             cat();
16171        }"
16172    ));
16173    cx.update_editor(|editor, window, cx| {
16174        editor.toggle_comments(toggle_comments, window, cx);
16175    });
16176    cx.assert_editor_state(indoc!(
16177        "fn a() {
16178             // dog();
16179             catˇ();
16180        }"
16181    ));
16182
16183    // Single selection on one line -> don't advance
16184    cx.set_state(indoc!(
16185        "fn a() {
16186             «dog()ˇ»;
16187             cat();
16188        }"
16189    ));
16190    cx.update_editor(|editor, window, cx| {
16191        editor.toggle_comments(toggle_comments, window, cx);
16192    });
16193    cx.assert_editor_state(indoc!(
16194        "fn a() {
16195             // «dog()ˇ»;
16196             cat();
16197        }"
16198    ));
16199
16200    // Multiple cursors on one line -> advance
16201    cx.set_state(indoc!(
16202        "fn a() {
16203             ˇdˇog();
16204             cat();
16205        }"
16206    ));
16207    cx.update_editor(|editor, window, cx| {
16208        editor.toggle_comments(toggle_comments, window, cx);
16209    });
16210    cx.assert_editor_state(indoc!(
16211        "fn a() {
16212             // dog();
16213             catˇ(ˇ);
16214        }"
16215    ));
16216
16217    // Multiple cursors on one line, with selection -> don't advance
16218    cx.set_state(indoc!(
16219        "fn a() {
16220             ˇdˇog«()ˇ»;
16221             cat();
16222        }"
16223    ));
16224    cx.update_editor(|editor, window, cx| {
16225        editor.toggle_comments(toggle_comments, window, cx);
16226    });
16227    cx.assert_editor_state(indoc!(
16228        "fn a() {
16229             // ˇdˇog«()ˇ»;
16230             cat();
16231        }"
16232    ));
16233
16234    // Single cursor on one line -> advance
16235    // Cursor moves to column 0 on blank line
16236    cx.set_state(indoc!(
16237        "fn a() {
16238             ˇdog();
16239
16240             cat();
16241        }"
16242    ));
16243    cx.update_editor(|editor, window, cx| {
16244        editor.toggle_comments(toggle_comments, window, cx);
16245    });
16246    cx.assert_editor_state(indoc!(
16247        "fn a() {
16248             // dog();
16249        ˇ
16250             cat();
16251        }"
16252    ));
16253
16254    // Single cursor on one line -> advance
16255    // Cursor starts and ends at column 0
16256    cx.set_state(indoc!(
16257        "fn a() {
16258         ˇ    dog();
16259             cat();
16260        }"
16261    ));
16262    cx.update_editor(|editor, window, cx| {
16263        editor.toggle_comments(toggle_comments, window, cx);
16264    });
16265    cx.assert_editor_state(indoc!(
16266        "fn a() {
16267             // dog();
16268         ˇ    cat();
16269        }"
16270    ));
16271}
16272
16273#[gpui::test]
16274async fn test_toggle_block_comment(cx: &mut TestAppContext) {
16275    init_test(cx, |_| {});
16276
16277    let mut cx = EditorTestContext::new(cx).await;
16278
16279    let html_language = Arc::new(
16280        Language::new(
16281            LanguageConfig {
16282                name: "HTML".into(),
16283                block_comment: Some(BlockCommentConfig {
16284                    start: "<!-- ".into(),
16285                    prefix: "".into(),
16286                    end: " -->".into(),
16287                    tab_size: 0,
16288                }),
16289                ..Default::default()
16290            },
16291            Some(tree_sitter_html::LANGUAGE.into()),
16292        )
16293        .with_injection_query(
16294            r#"
16295            (script_element
16296                (raw_text) @injection.content
16297                (#set! injection.language "javascript"))
16298            "#,
16299        )
16300        .unwrap(),
16301    );
16302
16303    let javascript_language = Arc::new(Language::new(
16304        LanguageConfig {
16305            name: "JavaScript".into(),
16306            line_comments: vec!["// ".into()],
16307            ..Default::default()
16308        },
16309        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
16310    ));
16311
16312    cx.language_registry().add(html_language.clone());
16313    cx.language_registry().add(javascript_language);
16314    cx.update_buffer(|buffer, cx| {
16315        buffer.set_language_immediate(Some(html_language), cx);
16316    });
16317
16318    // Toggle comments for empty selections
16319    cx.set_state(
16320        &r#"
16321            <p>A</p>ˇ
16322            <p>B</p>ˇ
16323            <p>C</p>ˇ
16324        "#
16325        .unindent(),
16326    );
16327    cx.update_editor(|editor, window, cx| {
16328        editor.toggle_comments(&ToggleComments::default(), window, cx)
16329    });
16330    cx.assert_editor_state(
16331        &r#"
16332            <!-- <p>A</p>ˇ -->
16333            <!-- <p>B</p>ˇ -->
16334            <!-- <p>C</p>ˇ -->
16335        "#
16336        .unindent(),
16337    );
16338    cx.update_editor(|editor, window, cx| {
16339        editor.toggle_comments(&ToggleComments::default(), window, cx)
16340    });
16341    cx.assert_editor_state(
16342        &r#"
16343            <p>A</p>ˇ
16344            <p>B</p>ˇ
16345            <p>C</p>ˇ
16346        "#
16347        .unindent(),
16348    );
16349
16350    // Toggle comments for mixture of empty and non-empty selections, where
16351    // multiple selections occupy a given line.
16352    cx.set_state(
16353        &r#"
16354            <p>A«</p>
16355            <p>ˇ»B</p>ˇ
16356            <p>C«</p>
16357            <p>ˇ»D</p>ˇ
16358        "#
16359        .unindent(),
16360    );
16361
16362    cx.update_editor(|editor, window, cx| {
16363        editor.toggle_comments(&ToggleComments::default(), window, cx)
16364    });
16365    cx.assert_editor_state(
16366        &r#"
16367            <!-- <p>A«</p>
16368            <p>ˇ»B</p>ˇ -->
16369            <!-- <p>C«</p>
16370            <p>ˇ»D</p>ˇ -->
16371        "#
16372        .unindent(),
16373    );
16374    cx.update_editor(|editor, window, cx| {
16375        editor.toggle_comments(&ToggleComments::default(), window, cx)
16376    });
16377    cx.assert_editor_state(
16378        &r#"
16379            <p>A«</p>
16380            <p>ˇ»B</p>ˇ
16381            <p>C«</p>
16382            <p>ˇ»D</p>ˇ
16383        "#
16384        .unindent(),
16385    );
16386
16387    // Toggle comments when different languages are active for different
16388    // selections.
16389    cx.set_state(
16390        &r#"
16391            ˇ<script>
16392                ˇvar x = new Y();
16393            ˇ</script>
16394        "#
16395        .unindent(),
16396    );
16397    cx.executor().run_until_parked();
16398    cx.update_editor(|editor, window, cx| {
16399        editor.toggle_comments(&ToggleComments::default(), window, cx)
16400    });
16401    // TODO this is how it actually worked in Zed Stable, which is not very ergonomic.
16402    // Uncommenting and commenting from this position brings in even more wrong artifacts.
16403    cx.assert_editor_state(
16404        &r#"
16405            <!-- ˇ<script> -->
16406                // ˇvar x = new Y();
16407            <!-- ˇ</script> -->
16408        "#
16409        .unindent(),
16410    );
16411}
16412
16413#[gpui::test]
16414fn test_editing_disjoint_excerpts(cx: &mut TestAppContext) {
16415    init_test(cx, |_| {});
16416
16417    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16418    let multibuffer = cx.new(|cx| {
16419        let mut multibuffer = MultiBuffer::new(ReadWrite);
16420        multibuffer.push_excerpts(
16421            buffer.clone(),
16422            [
16423                ExcerptRange::new(Point::new(0, 0)..Point::new(0, 4)),
16424                ExcerptRange::new(Point::new(1, 0)..Point::new(1, 4)),
16425            ],
16426            cx,
16427        );
16428        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb");
16429        multibuffer
16430    });
16431
16432    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16433    editor.update_in(cx, |editor, window, cx| {
16434        assert_eq!(editor.text(cx), "aaaa\nbbbb");
16435        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16436            s.select_ranges([
16437                Point::new(0, 0)..Point::new(0, 0),
16438                Point::new(1, 0)..Point::new(1, 0),
16439            ])
16440        });
16441
16442        editor.handle_input("X", window, cx);
16443        assert_eq!(editor.text(cx), "Xaaaa\nXbbbb");
16444        assert_eq!(
16445            editor.selections.ranges(&editor.display_snapshot(cx)),
16446            [
16447                Point::new(0, 1)..Point::new(0, 1),
16448                Point::new(1, 1)..Point::new(1, 1),
16449            ]
16450        );
16451
16452        // Ensure the cursor's head is respected when deleting across an excerpt boundary.
16453        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16454            s.select_ranges([Point::new(0, 2)..Point::new(1, 2)])
16455        });
16456        editor.backspace(&Default::default(), window, cx);
16457        assert_eq!(editor.text(cx), "Xa\nbbb");
16458        assert_eq!(
16459            editor.selections.ranges(&editor.display_snapshot(cx)),
16460            [Point::new(1, 0)..Point::new(1, 0)]
16461        );
16462
16463        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16464            s.select_ranges([Point::new(1, 1)..Point::new(0, 1)])
16465        });
16466        editor.backspace(&Default::default(), window, cx);
16467        assert_eq!(editor.text(cx), "X\nbb");
16468        assert_eq!(
16469            editor.selections.ranges(&editor.display_snapshot(cx)),
16470            [Point::new(0, 1)..Point::new(0, 1)]
16471        );
16472    });
16473}
16474
16475#[gpui::test]
16476fn test_editing_overlapping_excerpts(cx: &mut TestAppContext) {
16477    init_test(cx, |_| {});
16478
16479    let markers = vec![('[', ']').into(), ('(', ')').into()];
16480    let (initial_text, mut excerpt_ranges) = marked_text_ranges_by(
16481        indoc! {"
16482            [aaaa
16483            (bbbb]
16484            cccc)",
16485        },
16486        markers.clone(),
16487    );
16488    let excerpt_ranges = markers.into_iter().map(|marker| {
16489        let context = excerpt_ranges.remove(&marker).unwrap()[0].clone();
16490        ExcerptRange::new(context)
16491    });
16492    let buffer = cx.new(|cx| Buffer::local(initial_text, cx));
16493    let multibuffer = cx.new(|cx| {
16494        let mut multibuffer = MultiBuffer::new(ReadWrite);
16495        multibuffer.push_excerpts(buffer, excerpt_ranges, cx);
16496        multibuffer
16497    });
16498
16499    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(multibuffer, window, cx));
16500    editor.update_in(cx, |editor, window, cx| {
16501        let (expected_text, selection_ranges) = marked_text_ranges(
16502            indoc! {"
16503                aaaa
16504                bˇbbb
16505                bˇbbˇb
16506                cccc"
16507            },
16508            true,
16509        );
16510        assert_eq!(editor.text(cx), expected_text);
16511        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16512            s.select_ranges(
16513                selection_ranges
16514                    .iter()
16515                    .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end)),
16516            )
16517        });
16518
16519        editor.handle_input("X", window, cx);
16520
16521        let (expected_text, expected_selections) = marked_text_ranges(
16522            indoc! {"
16523                aaaa
16524                bXˇbbXb
16525                bXˇbbXˇb
16526                cccc"
16527            },
16528            false,
16529        );
16530        assert_eq!(editor.text(cx), expected_text);
16531        assert_eq!(
16532            editor.selections.ranges(&editor.display_snapshot(cx)),
16533            expected_selections
16534                .iter()
16535                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16536                .collect::<Vec<_>>()
16537        );
16538
16539        editor.newline(&Newline, window, cx);
16540        let (expected_text, expected_selections) = marked_text_ranges(
16541            indoc! {"
16542                aaaa
16543                bX
16544                ˇbbX
16545                b
16546                bX
16547                ˇbbX
16548                ˇb
16549                cccc"
16550            },
16551            false,
16552        );
16553        assert_eq!(editor.text(cx), expected_text);
16554        assert_eq!(
16555            editor.selections.ranges(&editor.display_snapshot(cx)),
16556            expected_selections
16557                .iter()
16558                .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
16559                .collect::<Vec<_>>()
16560        );
16561    });
16562}
16563
16564#[gpui::test]
16565fn test_refresh_selections(cx: &mut TestAppContext) {
16566    init_test(cx, |_| {});
16567
16568    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16569    let mut excerpt1_id = None;
16570    let multibuffer = cx.new(|cx| {
16571        let mut multibuffer = MultiBuffer::new(ReadWrite);
16572        excerpt1_id = multibuffer
16573            .push_excerpts(
16574                buffer.clone(),
16575                [
16576                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16577                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16578                ],
16579                cx,
16580            )
16581            .into_iter()
16582            .next();
16583        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16584        multibuffer
16585    });
16586
16587    let editor = cx.add_window(|window, cx| {
16588        let mut editor = build_editor(multibuffer.clone(), window, cx);
16589        let snapshot = editor.snapshot(window, cx);
16590        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16591            s.select_ranges([Point::new(1, 3)..Point::new(1, 3)])
16592        });
16593        editor.begin_selection(
16594            Point::new(2, 1).to_display_point(&snapshot),
16595            true,
16596            1,
16597            window,
16598            cx,
16599        );
16600        assert_eq!(
16601            editor.selections.ranges(&editor.display_snapshot(cx)),
16602            [
16603                Point::new(1, 3)..Point::new(1, 3),
16604                Point::new(2, 1)..Point::new(2, 1),
16605            ]
16606        );
16607        editor
16608    });
16609
16610    // Refreshing selections is a no-op when excerpts haven't changed.
16611    _ = editor.update(cx, |editor, window, cx| {
16612        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16613        assert_eq!(
16614            editor.selections.ranges(&editor.display_snapshot(cx)),
16615            [
16616                Point::new(1, 3)..Point::new(1, 3),
16617                Point::new(2, 1)..Point::new(2, 1),
16618            ]
16619        );
16620    });
16621
16622    multibuffer.update(cx, |multibuffer, cx| {
16623        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16624    });
16625    _ = editor.update(cx, |editor, window, cx| {
16626        // Removing an excerpt causes the first selection to become degenerate.
16627        assert_eq!(
16628            editor.selections.ranges(&editor.display_snapshot(cx)),
16629            [
16630                Point::new(0, 0)..Point::new(0, 0),
16631                Point::new(0, 1)..Point::new(0, 1)
16632            ]
16633        );
16634
16635        // Refreshing selections will relocate the first selection to the original buffer
16636        // location.
16637        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16638        assert_eq!(
16639            editor.selections.ranges(&editor.display_snapshot(cx)),
16640            [
16641                Point::new(0, 1)..Point::new(0, 1),
16642                Point::new(0, 3)..Point::new(0, 3)
16643            ]
16644        );
16645        assert!(editor.selections.pending_anchor().is_some());
16646    });
16647}
16648
16649#[gpui::test]
16650fn test_refresh_selections_while_selecting_with_mouse(cx: &mut TestAppContext) {
16651    init_test(cx, |_| {});
16652
16653    let buffer = cx.new(|cx| Buffer::local(sample_text(3, 4, 'a'), cx));
16654    let mut excerpt1_id = None;
16655    let multibuffer = cx.new(|cx| {
16656        let mut multibuffer = MultiBuffer::new(ReadWrite);
16657        excerpt1_id = multibuffer
16658            .push_excerpts(
16659                buffer.clone(),
16660                [
16661                    ExcerptRange::new(Point::new(0, 0)..Point::new(1, 4)),
16662                    ExcerptRange::new(Point::new(1, 0)..Point::new(2, 4)),
16663                ],
16664                cx,
16665            )
16666            .into_iter()
16667            .next();
16668        assert_eq!(multibuffer.read(cx).text(), "aaaa\nbbbb\nbbbb\ncccc");
16669        multibuffer
16670    });
16671
16672    let editor = cx.add_window(|window, cx| {
16673        let mut editor = build_editor(multibuffer.clone(), window, cx);
16674        let snapshot = editor.snapshot(window, cx);
16675        editor.begin_selection(
16676            Point::new(1, 3).to_display_point(&snapshot),
16677            false,
16678            1,
16679            window,
16680            cx,
16681        );
16682        assert_eq!(
16683            editor.selections.ranges(&editor.display_snapshot(cx)),
16684            [Point::new(1, 3)..Point::new(1, 3)]
16685        );
16686        editor
16687    });
16688
16689    multibuffer.update(cx, |multibuffer, cx| {
16690        multibuffer.remove_excerpts([excerpt1_id.unwrap()], cx);
16691    });
16692    _ = editor.update(cx, |editor, window, cx| {
16693        assert_eq!(
16694            editor.selections.ranges(&editor.display_snapshot(cx)),
16695            [Point::new(0, 0)..Point::new(0, 0)]
16696        );
16697
16698        // Ensure we don't panic when selections are refreshed and that the pending selection is finalized.
16699        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| s.refresh());
16700        assert_eq!(
16701            editor.selections.ranges(&editor.display_snapshot(cx)),
16702            [Point::new(0, 3)..Point::new(0, 3)]
16703        );
16704        assert!(editor.selections.pending_anchor().is_some());
16705    });
16706}
16707
16708#[gpui::test]
16709async fn test_extra_newline_insertion(cx: &mut TestAppContext) {
16710    init_test(cx, |_| {});
16711
16712    let language = Arc::new(
16713        Language::new(
16714            LanguageConfig {
16715                brackets: BracketPairConfig {
16716                    pairs: vec![
16717                        BracketPair {
16718                            start: "{".to_string(),
16719                            end: "}".to_string(),
16720                            close: true,
16721                            surround: true,
16722                            newline: true,
16723                        },
16724                        BracketPair {
16725                            start: "/* ".to_string(),
16726                            end: " */".to_string(),
16727                            close: true,
16728                            surround: true,
16729                            newline: true,
16730                        },
16731                    ],
16732                    ..Default::default()
16733                },
16734                ..Default::default()
16735            },
16736            Some(tree_sitter_rust::LANGUAGE.into()),
16737        )
16738        .with_indents_query("")
16739        .unwrap(),
16740    );
16741
16742    let text = concat!(
16743        "{   }\n",     //
16744        "  x\n",       //
16745        "  /*   */\n", //
16746        "x\n",         //
16747        "{{} }\n",     //
16748    );
16749
16750    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
16751    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
16752    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
16753    editor
16754        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
16755        .await;
16756
16757    editor.update_in(cx, |editor, window, cx| {
16758        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16759            s.select_display_ranges([
16760                DisplayPoint::new(DisplayRow(0), 2)..DisplayPoint::new(DisplayRow(0), 3),
16761                DisplayPoint::new(DisplayRow(2), 5)..DisplayPoint::new(DisplayRow(2), 5),
16762                DisplayPoint::new(DisplayRow(4), 4)..DisplayPoint::new(DisplayRow(4), 4),
16763            ])
16764        });
16765        editor.newline(&Newline, window, cx);
16766
16767        assert_eq!(
16768            editor.buffer().read(cx).read(cx).text(),
16769            concat!(
16770                "{ \n",    // Suppress rustfmt
16771                "\n",      //
16772                "}\n",     //
16773                "  x\n",   //
16774                "  /* \n", //
16775                "  \n",    //
16776                "  */\n",  //
16777                "x\n",     //
16778                "{{} \n",  //
16779                "}\n",     //
16780            )
16781        );
16782    });
16783}
16784
16785#[gpui::test]
16786fn test_highlighted_ranges(cx: &mut TestAppContext) {
16787    init_test(cx, |_| {});
16788
16789    let editor = cx.add_window(|window, cx| {
16790        let buffer = MultiBuffer::build_simple(&sample_text(16, 8, 'a'), cx);
16791        build_editor(buffer, window, cx)
16792    });
16793
16794    _ = editor.update(cx, |editor, window, cx| {
16795        struct Type1;
16796        struct Type2;
16797
16798        let buffer = editor.buffer.read(cx).snapshot(cx);
16799
16800        let anchor_range =
16801            |range: Range<Point>| buffer.anchor_after(range.start)..buffer.anchor_after(range.end);
16802
16803        editor.highlight_background::<Type1>(
16804            &[
16805                anchor_range(Point::new(2, 1)..Point::new(2, 3)),
16806                anchor_range(Point::new(4, 2)..Point::new(4, 4)),
16807                anchor_range(Point::new(6, 3)..Point::new(6, 5)),
16808                anchor_range(Point::new(8, 4)..Point::new(8, 6)),
16809            ],
16810            |_| Hsla::red(),
16811            cx,
16812        );
16813        editor.highlight_background::<Type2>(
16814            &[
16815                anchor_range(Point::new(3, 2)..Point::new(3, 5)),
16816                anchor_range(Point::new(5, 3)..Point::new(5, 6)),
16817                anchor_range(Point::new(7, 4)..Point::new(7, 7)),
16818                anchor_range(Point::new(9, 5)..Point::new(9, 8)),
16819            ],
16820            |_| Hsla::green(),
16821            cx,
16822        );
16823
16824        let snapshot = editor.snapshot(window, cx);
16825        let highlighted_ranges = editor.sorted_background_highlights_in_range(
16826            anchor_range(Point::new(3, 4)..Point::new(7, 4)),
16827            &snapshot,
16828            cx.theme(),
16829        );
16830        assert_eq!(
16831            highlighted_ranges,
16832            &[
16833                (
16834                    DisplayPoint::new(DisplayRow(3), 2)..DisplayPoint::new(DisplayRow(3), 5),
16835                    Hsla::green(),
16836                ),
16837                (
16838                    DisplayPoint::new(DisplayRow(4), 2)..DisplayPoint::new(DisplayRow(4), 4),
16839                    Hsla::red(),
16840                ),
16841                (
16842                    DisplayPoint::new(DisplayRow(5), 3)..DisplayPoint::new(DisplayRow(5), 6),
16843                    Hsla::green(),
16844                ),
16845                (
16846                    DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16847                    Hsla::red(),
16848                ),
16849            ]
16850        );
16851        assert_eq!(
16852            editor.sorted_background_highlights_in_range(
16853                anchor_range(Point::new(5, 6)..Point::new(6, 4)),
16854                &snapshot,
16855                cx.theme(),
16856            ),
16857            &[(
16858                DisplayPoint::new(DisplayRow(6), 3)..DisplayPoint::new(DisplayRow(6), 5),
16859                Hsla::red(),
16860            )]
16861        );
16862    });
16863}
16864
16865#[gpui::test]
16866async fn test_following(cx: &mut TestAppContext) {
16867    init_test(cx, |_| {});
16868
16869    let fs = FakeFs::new(cx.executor());
16870    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
16871
16872    let buffer = project.update(cx, |project, cx| {
16873        let buffer = project.create_local_buffer(&sample_text(16, 8, 'a'), None, false, cx);
16874        cx.new(|cx| MultiBuffer::singleton(buffer, cx))
16875    });
16876    let leader = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
16877    let follower = cx.update(|cx| {
16878        cx.open_window(
16879            WindowOptions {
16880                window_bounds: Some(WindowBounds::Windowed(Bounds::from_corners(
16881                    gpui::Point::new(px(0.), px(0.)),
16882                    gpui::Point::new(px(10.), px(80.)),
16883                ))),
16884                ..Default::default()
16885            },
16886            |window, cx| cx.new(|cx| build_editor(buffer.clone(), window, cx)),
16887        )
16888        .unwrap()
16889    });
16890
16891    let is_still_following = Rc::new(RefCell::new(true));
16892    let follower_edit_event_count = Rc::new(RefCell::new(0));
16893    let pending_update = Rc::new(RefCell::new(None));
16894    let leader_entity = leader.root(cx).unwrap();
16895    let follower_entity = follower.root(cx).unwrap();
16896    _ = follower.update(cx, {
16897        let update = pending_update.clone();
16898        let is_still_following = is_still_following.clone();
16899        let follower_edit_event_count = follower_edit_event_count.clone();
16900        |_, window, cx| {
16901            cx.subscribe_in(
16902                &leader_entity,
16903                window,
16904                move |_, leader, event, window, cx| {
16905                    leader.read(cx).add_event_to_update_proto(
16906                        event,
16907                        &mut update.borrow_mut(),
16908                        window,
16909                        cx,
16910                    );
16911                },
16912            )
16913            .detach();
16914
16915            cx.subscribe_in(
16916                &follower_entity,
16917                window,
16918                move |_, _, event: &EditorEvent, _window, _cx| {
16919                    if matches!(Editor::to_follow_event(event), Some(FollowEvent::Unfollow)) {
16920                        *is_still_following.borrow_mut() = false;
16921                    }
16922
16923                    if let EditorEvent::BufferEdited = event {
16924                        *follower_edit_event_count.borrow_mut() += 1;
16925                    }
16926                },
16927            )
16928            .detach();
16929        }
16930    });
16931
16932    // Update the selections only
16933    _ = leader.update(cx, |leader, window, cx| {
16934        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16935            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
16936        });
16937    });
16938    follower
16939        .update(cx, |follower, window, cx| {
16940            follower.apply_update_proto(
16941                &project,
16942                pending_update.borrow_mut().take().unwrap(),
16943                window,
16944                cx,
16945            )
16946        })
16947        .unwrap()
16948        .await
16949        .unwrap();
16950    _ = follower.update(cx, |follower, _, cx| {
16951        assert_eq!(
16952            follower.selections.ranges(&follower.display_snapshot(cx)),
16953            vec![MultiBufferOffset(1)..MultiBufferOffset(1)]
16954        );
16955    });
16956    assert!(*is_still_following.borrow());
16957    assert_eq!(*follower_edit_event_count.borrow(), 0);
16958
16959    // Update the scroll position only
16960    _ = leader.update(cx, |leader, window, cx| {
16961        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16962    });
16963    follower
16964        .update(cx, |follower, window, cx| {
16965            follower.apply_update_proto(
16966                &project,
16967                pending_update.borrow_mut().take().unwrap(),
16968                window,
16969                cx,
16970            )
16971        })
16972        .unwrap()
16973        .await
16974        .unwrap();
16975    assert_eq!(
16976        follower
16977            .update(cx, |follower, _, cx| follower.scroll_position(cx))
16978            .unwrap(),
16979        gpui::Point::new(1.5, 3.5)
16980    );
16981    assert!(*is_still_following.borrow());
16982    assert_eq!(*follower_edit_event_count.borrow(), 0);
16983
16984    // Update the selections and scroll position. The follower's scroll position is updated
16985    // via autoscroll, not via the leader's exact scroll position.
16986    _ = leader.update(cx, |leader, window, cx| {
16987        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16988            s.select_ranges([MultiBufferOffset(0)..MultiBufferOffset(0)])
16989        });
16990        leader.request_autoscroll(Autoscroll::newest(), cx);
16991        leader.set_scroll_position(gpui::Point::new(1.5, 3.5), window, cx);
16992    });
16993    follower
16994        .update(cx, |follower, window, cx| {
16995            follower.apply_update_proto(
16996                &project,
16997                pending_update.borrow_mut().take().unwrap(),
16998                window,
16999                cx,
17000            )
17001        })
17002        .unwrap()
17003        .await
17004        .unwrap();
17005    _ = follower.update(cx, |follower, _, cx| {
17006        assert_eq!(follower.scroll_position(cx), gpui::Point::new(1.5, 0.0));
17007        assert_eq!(
17008            follower.selections.ranges(&follower.display_snapshot(cx)),
17009            vec![MultiBufferOffset(0)..MultiBufferOffset(0)]
17010        );
17011    });
17012    assert!(*is_still_following.borrow());
17013
17014    // Creating a pending selection that precedes another selection
17015    _ = leader.update(cx, |leader, window, cx| {
17016        leader.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17017            s.select_ranges([MultiBufferOffset(1)..MultiBufferOffset(1)])
17018        });
17019        leader.begin_selection(DisplayPoint::new(DisplayRow(0), 0), true, 1, window, cx);
17020    });
17021    follower
17022        .update(cx, |follower, window, cx| {
17023            follower.apply_update_proto(
17024                &project,
17025                pending_update.borrow_mut().take().unwrap(),
17026                window,
17027                cx,
17028            )
17029        })
17030        .unwrap()
17031        .await
17032        .unwrap();
17033    _ = follower.update(cx, |follower, _, cx| {
17034        assert_eq!(
17035            follower.selections.ranges(&follower.display_snapshot(cx)),
17036            vec![
17037                MultiBufferOffset(0)..MultiBufferOffset(0),
17038                MultiBufferOffset(1)..MultiBufferOffset(1)
17039            ]
17040        );
17041    });
17042    assert!(*is_still_following.borrow());
17043
17044    // Extend the pending selection so that it surrounds another selection
17045    _ = leader.update(cx, |leader, window, cx| {
17046        leader.extend_selection(DisplayPoint::new(DisplayRow(0), 2), 1, window, cx);
17047    });
17048    follower
17049        .update(cx, |follower, window, cx| {
17050            follower.apply_update_proto(
17051                &project,
17052                pending_update.borrow_mut().take().unwrap(),
17053                window,
17054                cx,
17055            )
17056        })
17057        .unwrap()
17058        .await
17059        .unwrap();
17060    _ = follower.update(cx, |follower, _, cx| {
17061        assert_eq!(
17062            follower.selections.ranges(&follower.display_snapshot(cx)),
17063            vec![MultiBufferOffset(0)..MultiBufferOffset(2)]
17064        );
17065    });
17066
17067    // Scrolling locally breaks the follow
17068    _ = follower.update(cx, |follower, window, cx| {
17069        let top_anchor = follower
17070            .buffer()
17071            .read(cx)
17072            .read(cx)
17073            .anchor_after(MultiBufferOffset(0));
17074        follower.set_scroll_anchor(
17075            ScrollAnchor {
17076                anchor: top_anchor,
17077                offset: gpui::Point::new(0.0, 0.5),
17078            },
17079            window,
17080            cx,
17081        );
17082    });
17083    assert!(!(*is_still_following.borrow()));
17084}
17085
17086#[gpui::test]
17087async fn test_following_with_multiple_excerpts(cx: &mut TestAppContext) {
17088    init_test(cx, |_| {});
17089
17090    let fs = FakeFs::new(cx.executor());
17091    let project = Project::test(fs, ["/file.rs".as_ref()], cx).await;
17092    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17093    let pane = workspace
17094        .update(cx, |workspace, _, _| workspace.active_pane().clone())
17095        .unwrap();
17096
17097    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
17098
17099    let leader = pane.update_in(cx, |_, window, cx| {
17100        let multibuffer = cx.new(|_| MultiBuffer::new(ReadWrite));
17101        cx.new(|cx| build_editor(multibuffer.clone(), window, cx))
17102    });
17103
17104    // Start following the editor when it has no excerpts.
17105    let mut state_message =
17106        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17107    let workspace_entity = workspace.root(cx).unwrap();
17108    let follower_1 = cx
17109        .update_window(*workspace.deref(), |_, window, cx| {
17110            Editor::from_state_proto(
17111                workspace_entity,
17112                ViewId {
17113                    creator: CollaboratorId::PeerId(PeerId::default()),
17114                    id: 0,
17115                },
17116                &mut state_message,
17117                window,
17118                cx,
17119            )
17120        })
17121        .unwrap()
17122        .unwrap()
17123        .await
17124        .unwrap();
17125
17126    let update_message = Rc::new(RefCell::new(None));
17127    follower_1.update_in(cx, {
17128        let update = update_message.clone();
17129        |_, window, cx| {
17130            cx.subscribe_in(&leader, window, move |_, leader, event, window, cx| {
17131                leader.read(cx).add_event_to_update_proto(
17132                    event,
17133                    &mut update.borrow_mut(),
17134                    window,
17135                    cx,
17136                );
17137            })
17138            .detach();
17139        }
17140    });
17141
17142    let (buffer_1, buffer_2) = project.update(cx, |project, cx| {
17143        (
17144            project.create_local_buffer("abc\ndef\nghi\njkl\n", None, false, cx),
17145            project.create_local_buffer("mno\npqr\nstu\nvwx\n", None, false, cx),
17146        )
17147    });
17148
17149    // Insert some excerpts.
17150    leader.update(cx, |leader, cx| {
17151        leader.buffer.update(cx, |multibuffer, cx| {
17152            multibuffer.set_excerpts_for_path(
17153                PathKey::with_sort_prefix(1, rel_path("b.txt").into_arc()),
17154                buffer_1.clone(),
17155                vec![
17156                    Point::row_range(0..3),
17157                    Point::row_range(1..6),
17158                    Point::row_range(12..15),
17159                ],
17160                0,
17161                cx,
17162            );
17163            multibuffer.set_excerpts_for_path(
17164                PathKey::with_sort_prefix(1, rel_path("a.txt").into_arc()),
17165                buffer_2.clone(),
17166                vec![Point::row_range(0..6), Point::row_range(8..12)],
17167                0,
17168                cx,
17169            );
17170        });
17171    });
17172
17173    // Apply the update of adding the excerpts.
17174    follower_1
17175        .update_in(cx, |follower, window, cx| {
17176            follower.apply_update_proto(
17177                &project,
17178                update_message.borrow().clone().unwrap(),
17179                window,
17180                cx,
17181            )
17182        })
17183        .await
17184        .unwrap();
17185    assert_eq!(
17186        follower_1.update(cx, |editor, cx| editor.text(cx)),
17187        leader.update(cx, |editor, cx| editor.text(cx))
17188    );
17189    update_message.borrow_mut().take();
17190
17191    // Start following separately after it already has excerpts.
17192    let mut state_message =
17193        leader.update_in(cx, |leader, window, cx| leader.to_state_proto(window, cx));
17194    let workspace_entity = workspace.root(cx).unwrap();
17195    let follower_2 = cx
17196        .update_window(*workspace.deref(), |_, window, cx| {
17197            Editor::from_state_proto(
17198                workspace_entity,
17199                ViewId {
17200                    creator: CollaboratorId::PeerId(PeerId::default()),
17201                    id: 0,
17202                },
17203                &mut state_message,
17204                window,
17205                cx,
17206            )
17207        })
17208        .unwrap()
17209        .unwrap()
17210        .await
17211        .unwrap();
17212    assert_eq!(
17213        follower_2.update(cx, |editor, cx| editor.text(cx)),
17214        leader.update(cx, |editor, cx| editor.text(cx))
17215    );
17216
17217    // Remove some excerpts.
17218    leader.update(cx, |leader, cx| {
17219        leader.buffer.update(cx, |multibuffer, cx| {
17220            let excerpt_ids = multibuffer.excerpt_ids();
17221            multibuffer.remove_excerpts([excerpt_ids[1], excerpt_ids[2]], cx);
17222            multibuffer.remove_excerpts([excerpt_ids[0]], cx);
17223        });
17224    });
17225
17226    // Apply the update of removing the excerpts.
17227    follower_1
17228        .update_in(cx, |follower, window, cx| {
17229            follower.apply_update_proto(
17230                &project,
17231                update_message.borrow().clone().unwrap(),
17232                window,
17233                cx,
17234            )
17235        })
17236        .await
17237        .unwrap();
17238    follower_2
17239        .update_in(cx, |follower, window, cx| {
17240            follower.apply_update_proto(
17241                &project,
17242                update_message.borrow().clone().unwrap(),
17243                window,
17244                cx,
17245            )
17246        })
17247        .await
17248        .unwrap();
17249    update_message.borrow_mut().take();
17250    assert_eq!(
17251        follower_1.update(cx, |editor, cx| editor.text(cx)),
17252        leader.update(cx, |editor, cx| editor.text(cx))
17253    );
17254}
17255
17256#[gpui::test]
17257async fn go_to_prev_overlapping_diagnostic(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17258    init_test(cx, |_| {});
17259
17260    let mut cx = EditorTestContext::new(cx).await;
17261    let lsp_store =
17262        cx.update_editor(|editor, _, cx| editor.project().unwrap().read(cx).lsp_store());
17263
17264    cx.set_state(indoc! {"
17265        ˇfn func(abc def: i32) -> u32 {
17266        }
17267    "});
17268
17269    cx.update(|_, cx| {
17270        lsp_store.update(cx, |lsp_store, cx| {
17271            lsp_store
17272                .update_diagnostics(
17273                    LanguageServerId(0),
17274                    lsp::PublishDiagnosticsParams {
17275                        uri: lsp::Uri::from_file_path(path!("/root/file")).unwrap(),
17276                        version: None,
17277                        diagnostics: vec![
17278                            lsp::Diagnostic {
17279                                range: lsp::Range::new(
17280                                    lsp::Position::new(0, 11),
17281                                    lsp::Position::new(0, 12),
17282                                ),
17283                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17284                                ..Default::default()
17285                            },
17286                            lsp::Diagnostic {
17287                                range: lsp::Range::new(
17288                                    lsp::Position::new(0, 12),
17289                                    lsp::Position::new(0, 15),
17290                                ),
17291                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17292                                ..Default::default()
17293                            },
17294                            lsp::Diagnostic {
17295                                range: lsp::Range::new(
17296                                    lsp::Position::new(0, 25),
17297                                    lsp::Position::new(0, 28),
17298                                ),
17299                                severity: Some(lsp::DiagnosticSeverity::ERROR),
17300                                ..Default::default()
17301                            },
17302                        ],
17303                    },
17304                    None,
17305                    DiagnosticSourceKind::Pushed,
17306                    &[],
17307                    cx,
17308                )
17309                .unwrap()
17310        });
17311    });
17312
17313    executor.run_until_parked();
17314
17315    cx.update_editor(|editor, window, cx| {
17316        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17317    });
17318
17319    cx.assert_editor_state(indoc! {"
17320        fn func(abc def: i32) -> ˇu32 {
17321        }
17322    "});
17323
17324    cx.update_editor(|editor, window, cx| {
17325        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17326    });
17327
17328    cx.assert_editor_state(indoc! {"
17329        fn func(abc ˇdef: i32) -> u32 {
17330        }
17331    "});
17332
17333    cx.update_editor(|editor, window, cx| {
17334        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17335    });
17336
17337    cx.assert_editor_state(indoc! {"
17338        fn func(abcˇ def: i32) -> u32 {
17339        }
17340    "});
17341
17342    cx.update_editor(|editor, window, cx| {
17343        editor.go_to_prev_diagnostic(&GoToPreviousDiagnostic::default(), window, cx);
17344    });
17345
17346    cx.assert_editor_state(indoc! {"
17347        fn func(abc def: i32) -> ˇu32 {
17348        }
17349    "});
17350}
17351
17352#[gpui::test]
17353async fn test_go_to_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
17354    init_test(cx, |_| {});
17355
17356    let mut cx = EditorTestContext::new(cx).await;
17357
17358    let diff_base = r#"
17359        use some::mod;
17360
17361        const A: u32 = 42;
17362
17363        fn main() {
17364            println!("hello");
17365
17366            println!("world");
17367        }
17368        "#
17369    .unindent();
17370
17371    // Edits are modified, removed, modified, added
17372    cx.set_state(
17373        &r#"
17374        use some::modified;
17375
17376        ˇ
17377        fn main() {
17378            println!("hello there");
17379
17380            println!("around the");
17381            println!("world");
17382        }
17383        "#
17384        .unindent(),
17385    );
17386
17387    cx.set_head_text(&diff_base);
17388    executor.run_until_parked();
17389
17390    cx.update_editor(|editor, window, cx| {
17391        //Wrap around the bottom of the buffer
17392        for _ in 0..3 {
17393            editor.go_to_next_hunk(&GoToHunk, window, cx);
17394        }
17395    });
17396
17397    cx.assert_editor_state(
17398        &r#"
17399        ˇuse some::modified;
17400
17401
17402        fn main() {
17403            println!("hello there");
17404
17405            println!("around the");
17406            println!("world");
17407        }
17408        "#
17409        .unindent(),
17410    );
17411
17412    cx.update_editor(|editor, window, cx| {
17413        //Wrap around the top of the buffer
17414        for _ in 0..2 {
17415            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17416        }
17417    });
17418
17419    cx.assert_editor_state(
17420        &r#"
17421        use some::modified;
17422
17423
17424        fn main() {
17425        ˇ    println!("hello there");
17426
17427            println!("around the");
17428            println!("world");
17429        }
17430        "#
17431        .unindent(),
17432    );
17433
17434    cx.update_editor(|editor, window, cx| {
17435        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17436    });
17437
17438    cx.assert_editor_state(
17439        &r#"
17440        use some::modified;
17441
17442        ˇ
17443        fn main() {
17444            println!("hello there");
17445
17446            println!("around the");
17447            println!("world");
17448        }
17449        "#
17450        .unindent(),
17451    );
17452
17453    cx.update_editor(|editor, window, cx| {
17454        editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17455    });
17456
17457    cx.assert_editor_state(
17458        &r#"
17459        ˇuse some::modified;
17460
17461
17462        fn main() {
17463            println!("hello there");
17464
17465            println!("around the");
17466            println!("world");
17467        }
17468        "#
17469        .unindent(),
17470    );
17471
17472    cx.update_editor(|editor, window, cx| {
17473        for _ in 0..2 {
17474            editor.go_to_prev_hunk(&GoToPreviousHunk, window, cx);
17475        }
17476    });
17477
17478    cx.assert_editor_state(
17479        &r#"
17480        use some::modified;
17481
17482
17483        fn main() {
17484        ˇ    println!("hello there");
17485
17486            println!("around the");
17487            println!("world");
17488        }
17489        "#
17490        .unindent(),
17491    );
17492
17493    cx.update_editor(|editor, window, cx| {
17494        editor.fold(&Fold, window, cx);
17495    });
17496
17497    cx.update_editor(|editor, window, cx| {
17498        editor.go_to_next_hunk(&GoToHunk, window, cx);
17499    });
17500
17501    cx.assert_editor_state(
17502        &r#"
17503        ˇuse some::modified;
17504
17505
17506        fn main() {
17507            println!("hello there");
17508
17509            println!("around the");
17510            println!("world");
17511        }
17512        "#
17513        .unindent(),
17514    );
17515}
17516
17517#[test]
17518fn test_split_words() {
17519    fn split(text: &str) -> Vec<&str> {
17520        split_words(text).collect()
17521    }
17522
17523    assert_eq!(split("HelloWorld"), &["Hello", "World"]);
17524    assert_eq!(split("hello_world"), &["hello_", "world"]);
17525    assert_eq!(split("_hello_world_"), &["_", "hello_", "world_"]);
17526    assert_eq!(split("Hello_World"), &["Hello_", "World"]);
17527    assert_eq!(split("helloWOrld"), &["hello", "WOrld"]);
17528    assert_eq!(split("helloworld"), &["helloworld"]);
17529
17530    assert_eq!(split(":do_the_thing"), &[":", "do_", "the_", "thing"]);
17531}
17532
17533#[test]
17534fn test_split_words_for_snippet_prefix() {
17535    fn split(text: &str) -> Vec<&str> {
17536        snippet_candidate_suffixes(text, |c| c.is_alphanumeric() || c == '_').collect()
17537    }
17538
17539    assert_eq!(split("HelloWorld"), &["HelloWorld"]);
17540    assert_eq!(split("hello_world"), &["hello_world"]);
17541    assert_eq!(split("_hello_world_"), &["_hello_world_"]);
17542    assert_eq!(split("Hello_World"), &["Hello_World"]);
17543    assert_eq!(split("helloWOrld"), &["helloWOrld"]);
17544    assert_eq!(split("helloworld"), &["helloworld"]);
17545    assert_eq!(
17546        split("this@is!@#$^many   . symbols"),
17547        &[
17548            "symbols",
17549            " symbols",
17550            ". symbols",
17551            " . symbols",
17552            "  . symbols",
17553            "   . symbols",
17554            "many   . symbols",
17555            "^many   . symbols",
17556            "$^many   . symbols",
17557            "#$^many   . symbols",
17558            "@#$^many   . symbols",
17559            "!@#$^many   . symbols",
17560            "is!@#$^many   . symbols",
17561            "@is!@#$^many   . symbols",
17562            "this@is!@#$^many   . symbols",
17563        ],
17564    );
17565    assert_eq!(split("a.s"), &["s", ".s", "a.s"]);
17566}
17567
17568#[gpui::test]
17569async fn test_move_to_enclosing_bracket(cx: &mut TestAppContext) {
17570    init_test(cx, |_| {});
17571
17572    let mut cx = EditorLspTestContext::new_typescript(Default::default(), cx).await;
17573
17574    #[track_caller]
17575    fn assert(before: &str, after: &str, cx: &mut EditorLspTestContext) {
17576        let _state_context = cx.set_state(before);
17577        cx.run_until_parked();
17578        cx.update_editor(|editor, window, cx| {
17579            editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx)
17580        });
17581        cx.run_until_parked();
17582        cx.assert_editor_state(after);
17583    }
17584
17585    // Outside bracket jumps to outside of matching bracket
17586    assert("console.logˇ(var);", "console.log(var)ˇ;", &mut cx);
17587    assert("console.log(var)ˇ;", "console.logˇ(var);", &mut cx);
17588
17589    // Inside bracket jumps to inside of matching bracket
17590    assert("console.log(ˇvar);", "console.log(varˇ);", &mut cx);
17591    assert("console.log(varˇ);", "console.log(ˇvar);", &mut cx);
17592
17593    // When outside a bracket and inside, favor jumping to the inside bracket
17594    assert(
17595        "console.log('foo', [1, 2, 3]ˇ);",
17596        "console.log('foo', ˇ[1, 2, 3]);",
17597        &mut cx,
17598    );
17599    assert(
17600        "console.log(ˇ'foo', [1, 2, 3]);",
17601        "console.log('foo'ˇ, [1, 2, 3]);",
17602        &mut cx,
17603    );
17604
17605    // Bias forward if two options are equally likely
17606    assert(
17607        "let result = curried_fun()ˇ();",
17608        "let result = curried_fun()()ˇ;",
17609        &mut cx,
17610    );
17611
17612    // If directly adjacent to a smaller pair but inside a larger (not adjacent), pick the smaller
17613    assert(
17614        indoc! {"
17615            function test() {
17616                console.log('test')ˇ
17617            }"},
17618        indoc! {"
17619            function test() {
17620                console.logˇ('test')
17621            }"},
17622        &mut cx,
17623    );
17624}
17625
17626#[gpui::test]
17627async fn test_move_to_enclosing_bracket_in_markdown_code_block(cx: &mut TestAppContext) {
17628    init_test(cx, |_| {});
17629    let language_registry = Arc::new(language::LanguageRegistry::test(cx.executor()));
17630    language_registry.add(markdown_lang());
17631    language_registry.add(rust_lang());
17632    let buffer = cx.new(|cx| {
17633        let mut buffer = language::Buffer::local(
17634            indoc! {"
17635            ```rs
17636            impl Worktree {
17637                pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17638                }
17639            }
17640            ```
17641        "},
17642            cx,
17643        );
17644        buffer.set_language_registry(language_registry.clone());
17645        buffer.set_language_immediate(Some(markdown_lang()), cx);
17646        buffer
17647    });
17648    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
17649    let editor = cx.add_window(|window, cx| build_editor(buffer.clone(), window, cx));
17650    cx.executor().run_until_parked();
17651    _ = editor.update(cx, |editor, window, cx| {
17652        // Case 1: Test outer enclosing brackets
17653        select_ranges(
17654            editor,
17655            &indoc! {"
17656                ```rs
17657                impl Worktree {
17658                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17659                    }
1766017661                ```
17662            "},
17663            window,
17664            cx,
17665        );
17666        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17667        assert_text_with_selections(
17668            editor,
17669            &indoc! {"
17670                ```rs
17671                impl Worktree ˇ{
17672                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
17673                    }
17674                }
17675                ```
17676            "},
17677            cx,
17678        );
17679        // Case 2: Test inner enclosing brackets
17680        select_ranges(
17681            editor,
17682            &indoc! {"
17683                ```rs
17684                impl Worktree {
17685                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> {
1768617687                }
17688                ```
17689            "},
17690            window,
17691            cx,
17692        );
17693        editor.move_to_enclosing_bracket(&MoveToEnclosingBracket, window, cx);
17694        assert_text_with_selections(
17695            editor,
17696            &indoc! {"
17697                ```rs
17698                impl Worktree {
17699                    pub async fn open_buffers(&self, path: &Path) -> impl Iterator<&Buffer> ˇ{
17700                    }
17701                }
17702                ```
17703            "},
17704            cx,
17705        );
17706    });
17707}
17708
17709#[gpui::test]
17710async fn test_on_type_formatting_not_triggered(cx: &mut TestAppContext) {
17711    init_test(cx, |_| {});
17712
17713    let fs = FakeFs::new(cx.executor());
17714    fs.insert_tree(
17715        path!("/a"),
17716        json!({
17717            "main.rs": "fn main() { let a = 5; }",
17718            "other.rs": "// Test file",
17719        }),
17720    )
17721    .await;
17722    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17723
17724    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17725    language_registry.add(Arc::new(Language::new(
17726        LanguageConfig {
17727            name: "Rust".into(),
17728            matcher: LanguageMatcher {
17729                path_suffixes: vec!["rs".to_string()],
17730                ..Default::default()
17731            },
17732            brackets: BracketPairConfig {
17733                pairs: vec![BracketPair {
17734                    start: "{".to_string(),
17735                    end: "}".to_string(),
17736                    close: true,
17737                    surround: true,
17738                    newline: true,
17739                }],
17740                disabled_scopes_by_bracket_ix: Vec::new(),
17741            },
17742            ..Default::default()
17743        },
17744        Some(tree_sitter_rust::LANGUAGE.into()),
17745    )));
17746    let mut fake_servers = language_registry.register_fake_lsp(
17747        "Rust",
17748        FakeLspAdapter {
17749            capabilities: lsp::ServerCapabilities {
17750                document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17751                    first_trigger_character: "{".to_string(),
17752                    more_trigger_character: None,
17753                }),
17754                ..Default::default()
17755            },
17756            ..Default::default()
17757        },
17758    );
17759
17760    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17761
17762    let cx = &mut VisualTestContext::from_window(*workspace, cx);
17763
17764    let worktree_id = workspace
17765        .update(cx, |workspace, _, cx| {
17766            workspace.project().update(cx, |project, cx| {
17767                project.worktrees(cx).next().unwrap().read(cx).id()
17768            })
17769        })
17770        .unwrap();
17771
17772    let buffer = project
17773        .update(cx, |project, cx| {
17774            project.open_local_buffer(path!("/a/main.rs"), cx)
17775        })
17776        .await
17777        .unwrap();
17778    let editor_handle = workspace
17779        .update(cx, |workspace, window, cx| {
17780            workspace.open_path((worktree_id, rel_path("main.rs")), None, true, window, cx)
17781        })
17782        .unwrap()
17783        .await
17784        .unwrap()
17785        .downcast::<Editor>()
17786        .unwrap();
17787
17788    cx.executor().start_waiting();
17789    let fake_server = fake_servers.next().await.unwrap();
17790
17791    fake_server.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(
17792        |params, _| async move {
17793            assert_eq!(
17794                params.text_document_position.text_document.uri,
17795                lsp::Uri::from_file_path(path!("/a/main.rs")).unwrap(),
17796            );
17797            assert_eq!(
17798                params.text_document_position.position,
17799                lsp::Position::new(0, 21),
17800            );
17801
17802            Ok(Some(vec![lsp::TextEdit {
17803                new_text: "]".to_string(),
17804                range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
17805            }]))
17806        },
17807    );
17808
17809    editor_handle.update_in(cx, |editor, window, cx| {
17810        window.focus(&editor.focus_handle(cx));
17811        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17812            s.select_ranges([Point::new(0, 21)..Point::new(0, 20)])
17813        });
17814        editor.handle_input("{", window, cx);
17815    });
17816
17817    cx.executor().run_until_parked();
17818
17819    buffer.update(cx, |buffer, _| {
17820        assert_eq!(
17821            buffer.text(),
17822            "fn main() { let a = {5}; }",
17823            "No extra braces from on type formatting should appear in the buffer"
17824        )
17825    });
17826}
17827
17828#[gpui::test(iterations = 20, seeds(31))]
17829async fn test_on_type_formatting_is_applied_after_autoindent(cx: &mut TestAppContext) {
17830    init_test(cx, |_| {});
17831
17832    let mut cx = EditorLspTestContext::new_rust(
17833        lsp::ServerCapabilities {
17834            document_on_type_formatting_provider: Some(lsp::DocumentOnTypeFormattingOptions {
17835                first_trigger_character: ".".to_string(),
17836                more_trigger_character: None,
17837            }),
17838            ..Default::default()
17839        },
17840        cx,
17841    )
17842    .await;
17843
17844    cx.update_buffer(|buffer, _| {
17845        // This causes autoindent to be async.
17846        buffer.set_sync_parse_timeout(Duration::ZERO)
17847    });
17848
17849    cx.set_state("fn c() {\n    d()ˇ\n}\n");
17850    cx.simulate_keystroke("\n");
17851    cx.run_until_parked();
17852
17853    let buffer_cloned = cx.multibuffer(|multi_buffer, _| multi_buffer.as_singleton().unwrap());
17854    let mut request =
17855        cx.set_request_handler::<lsp::request::OnTypeFormatting, _, _>(move |_, _, mut cx| {
17856            let buffer_cloned = buffer_cloned.clone();
17857            async move {
17858                buffer_cloned.update(&mut cx, |buffer, _| {
17859                    assert_eq!(
17860                        buffer.text(),
17861                        "fn c() {\n    d()\n        .\n}\n",
17862                        "OnTypeFormatting should triggered after autoindent applied"
17863                    )
17864                })?;
17865
17866                Ok(Some(vec![]))
17867            }
17868        });
17869
17870    cx.simulate_keystroke(".");
17871    cx.run_until_parked();
17872
17873    cx.assert_editor_state("fn c() {\n    d()\n\n}\n");
17874    assert!(request.next().await.is_some());
17875    request.close();
17876    assert!(request.next().await.is_none());
17877}
17878
17879#[gpui::test]
17880async fn test_language_server_restart_due_to_settings_change(cx: &mut TestAppContext) {
17881    init_test(cx, |_| {});
17882
17883    let fs = FakeFs::new(cx.executor());
17884    fs.insert_tree(
17885        path!("/a"),
17886        json!({
17887            "main.rs": "fn main() { let a = 5; }",
17888            "other.rs": "// Test file",
17889        }),
17890    )
17891    .await;
17892
17893    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
17894
17895    let server_restarts = Arc::new(AtomicUsize::new(0));
17896    let closure_restarts = Arc::clone(&server_restarts);
17897    let language_server_name = "test language server";
17898    let language_name: LanguageName = "Rust".into();
17899
17900    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
17901    language_registry.add(Arc::new(Language::new(
17902        LanguageConfig {
17903            name: language_name.clone(),
17904            matcher: LanguageMatcher {
17905                path_suffixes: vec!["rs".to_string()],
17906                ..Default::default()
17907            },
17908            ..Default::default()
17909        },
17910        Some(tree_sitter_rust::LANGUAGE.into()),
17911    )));
17912    let mut fake_servers = language_registry.register_fake_lsp(
17913        "Rust",
17914        FakeLspAdapter {
17915            name: language_server_name,
17916            initialization_options: Some(json!({
17917                "testOptionValue": true
17918            })),
17919            initializer: Some(Box::new(move |fake_server| {
17920                let task_restarts = Arc::clone(&closure_restarts);
17921                fake_server.set_request_handler::<lsp::request::Shutdown, _, _>(move |_, _| {
17922                    task_restarts.fetch_add(1, atomic::Ordering::Release);
17923                    futures::future::ready(Ok(()))
17924                });
17925            })),
17926            ..Default::default()
17927        },
17928    );
17929
17930    let _window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
17931    let _buffer = project
17932        .update(cx, |project, cx| {
17933            project.open_local_buffer_with_lsp(path!("/a/main.rs"), cx)
17934        })
17935        .await
17936        .unwrap();
17937    let _fake_server = fake_servers.next().await.unwrap();
17938    update_test_language_settings(cx, |language_settings| {
17939        language_settings.languages.0.insert(
17940            language_name.clone().0,
17941            LanguageSettingsContent {
17942                tab_size: NonZeroU32::new(8),
17943                ..Default::default()
17944            },
17945        );
17946    });
17947    cx.executor().run_until_parked();
17948    assert_eq!(
17949        server_restarts.load(atomic::Ordering::Acquire),
17950        0,
17951        "Should not restart LSP server on an unrelated change"
17952    );
17953
17954    update_test_project_settings(cx, |project_settings| {
17955        project_settings.lsp.insert(
17956            "Some other server name".into(),
17957            LspSettings {
17958                binary: None,
17959                settings: None,
17960                initialization_options: Some(json!({
17961                    "some other init value": false
17962                })),
17963                enable_lsp_tasks: false,
17964                fetch: None,
17965            },
17966        );
17967    });
17968    cx.executor().run_until_parked();
17969    assert_eq!(
17970        server_restarts.load(atomic::Ordering::Acquire),
17971        0,
17972        "Should not restart LSP server on an unrelated LSP settings change"
17973    );
17974
17975    update_test_project_settings(cx, |project_settings| {
17976        project_settings.lsp.insert(
17977            language_server_name.into(),
17978            LspSettings {
17979                binary: None,
17980                settings: None,
17981                initialization_options: Some(json!({
17982                    "anotherInitValue": false
17983                })),
17984                enable_lsp_tasks: false,
17985                fetch: None,
17986            },
17987        );
17988    });
17989    cx.executor().run_until_parked();
17990    assert_eq!(
17991        server_restarts.load(atomic::Ordering::Acquire),
17992        1,
17993        "Should restart LSP server on a related LSP settings change"
17994    );
17995
17996    update_test_project_settings(cx, |project_settings| {
17997        project_settings.lsp.insert(
17998            language_server_name.into(),
17999            LspSettings {
18000                binary: None,
18001                settings: None,
18002                initialization_options: Some(json!({
18003                    "anotherInitValue": false
18004                })),
18005                enable_lsp_tasks: false,
18006                fetch: None,
18007            },
18008        );
18009    });
18010    cx.executor().run_until_parked();
18011    assert_eq!(
18012        server_restarts.load(atomic::Ordering::Acquire),
18013        1,
18014        "Should not restart LSP server on a related LSP settings change that is the same"
18015    );
18016
18017    update_test_project_settings(cx, |project_settings| {
18018        project_settings.lsp.insert(
18019            language_server_name.into(),
18020            LspSettings {
18021                binary: None,
18022                settings: None,
18023                initialization_options: None,
18024                enable_lsp_tasks: false,
18025                fetch: None,
18026            },
18027        );
18028    });
18029    cx.executor().run_until_parked();
18030    assert_eq!(
18031        server_restarts.load(atomic::Ordering::Acquire),
18032        2,
18033        "Should restart LSP server on another related LSP settings change"
18034    );
18035}
18036
18037#[gpui::test]
18038async fn test_completions_with_additional_edits(cx: &mut TestAppContext) {
18039    init_test(cx, |_| {});
18040
18041    let mut cx = EditorLspTestContext::new_rust(
18042        lsp::ServerCapabilities {
18043            completion_provider: Some(lsp::CompletionOptions {
18044                trigger_characters: Some(vec![".".to_string()]),
18045                resolve_provider: Some(true),
18046                ..Default::default()
18047            }),
18048            ..Default::default()
18049        },
18050        cx,
18051    )
18052    .await;
18053
18054    cx.set_state("fn main() { let a = 2ˇ; }");
18055    cx.simulate_keystroke(".");
18056    let completion_item = lsp::CompletionItem {
18057        label: "some".into(),
18058        kind: Some(lsp::CompletionItemKind::SNIPPET),
18059        detail: Some("Wrap the expression in an `Option::Some`".to_string()),
18060        documentation: Some(lsp::Documentation::MarkupContent(lsp::MarkupContent {
18061            kind: lsp::MarkupKind::Markdown,
18062            value: "```rust\nSome(2)\n```".to_string(),
18063        })),
18064        deprecated: Some(false),
18065        sort_text: Some("fffffff2".to_string()),
18066        filter_text: Some("some".to_string()),
18067        insert_text_format: Some(lsp::InsertTextFormat::SNIPPET),
18068        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18069            range: lsp::Range {
18070                start: lsp::Position {
18071                    line: 0,
18072                    character: 22,
18073                },
18074                end: lsp::Position {
18075                    line: 0,
18076                    character: 22,
18077                },
18078            },
18079            new_text: "Some(2)".to_string(),
18080        })),
18081        additional_text_edits: Some(vec![lsp::TextEdit {
18082            range: lsp::Range {
18083                start: lsp::Position {
18084                    line: 0,
18085                    character: 20,
18086                },
18087                end: lsp::Position {
18088                    line: 0,
18089                    character: 22,
18090                },
18091            },
18092            new_text: "".to_string(),
18093        }]),
18094        ..Default::default()
18095    };
18096
18097    let closure_completion_item = completion_item.clone();
18098    let mut request = cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18099        let task_completion_item = closure_completion_item.clone();
18100        async move {
18101            Ok(Some(lsp::CompletionResponse::Array(vec![
18102                task_completion_item,
18103            ])))
18104        }
18105    });
18106
18107    request.next().await;
18108
18109    cx.condition(|editor, _| editor.context_menu_visible())
18110        .await;
18111    let apply_additional_edits = cx.update_editor(|editor, window, cx| {
18112        editor
18113            .confirm_completion(&ConfirmCompletion::default(), window, cx)
18114            .unwrap()
18115    });
18116    cx.assert_editor_state("fn main() { let a = 2.Some(2)ˇ; }");
18117
18118    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
18119        let task_completion_item = completion_item.clone();
18120        async move { Ok(task_completion_item) }
18121    })
18122    .next()
18123    .await
18124    .unwrap();
18125    apply_additional_edits.await.unwrap();
18126    cx.assert_editor_state("fn main() { let a = Some(2)ˇ; }");
18127}
18128
18129#[gpui::test]
18130async fn test_completions_resolve_updates_labels_if_filter_text_matches(cx: &mut TestAppContext) {
18131    init_test(cx, |_| {});
18132
18133    let mut cx = EditorLspTestContext::new_rust(
18134        lsp::ServerCapabilities {
18135            completion_provider: Some(lsp::CompletionOptions {
18136                trigger_characters: Some(vec![".".to_string()]),
18137                resolve_provider: Some(true),
18138                ..Default::default()
18139            }),
18140            ..Default::default()
18141        },
18142        cx,
18143    )
18144    .await;
18145
18146    cx.set_state("fn main() { let a = 2ˇ; }");
18147    cx.simulate_keystroke(".");
18148
18149    let item1 = lsp::CompletionItem {
18150        label: "method id()".to_string(),
18151        filter_text: Some("id".to_string()),
18152        detail: None,
18153        documentation: None,
18154        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18155            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18156            new_text: ".id".to_string(),
18157        })),
18158        ..lsp::CompletionItem::default()
18159    };
18160
18161    let item2 = lsp::CompletionItem {
18162        label: "other".to_string(),
18163        filter_text: Some("other".to_string()),
18164        detail: None,
18165        documentation: None,
18166        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18167            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18168            new_text: ".other".to_string(),
18169        })),
18170        ..lsp::CompletionItem::default()
18171    };
18172
18173    let item1 = item1.clone();
18174    cx.set_request_handler::<lsp::request::Completion, _, _>({
18175        let item1 = item1.clone();
18176        move |_, _, _| {
18177            let item1 = item1.clone();
18178            let item2 = item2.clone();
18179            async move { Ok(Some(lsp::CompletionResponse::Array(vec![item1, item2]))) }
18180        }
18181    })
18182    .next()
18183    .await;
18184
18185    cx.condition(|editor, _| editor.context_menu_visible())
18186        .await;
18187    cx.update_editor(|editor, _, _| {
18188        let context_menu = editor.context_menu.borrow_mut();
18189        let context_menu = context_menu
18190            .as_ref()
18191            .expect("Should have the context menu deployed");
18192        match context_menu {
18193            CodeContextMenu::Completions(completions_menu) => {
18194                let completions = completions_menu.completions.borrow_mut();
18195                assert_eq!(
18196                    completions
18197                        .iter()
18198                        .map(|completion| &completion.label.text)
18199                        .collect::<Vec<_>>(),
18200                    vec!["method id()", "other"]
18201                )
18202            }
18203            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18204        }
18205    });
18206
18207    cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>({
18208        let item1 = item1.clone();
18209        move |_, item_to_resolve, _| {
18210            let item1 = item1.clone();
18211            async move {
18212                if item1 == item_to_resolve {
18213                    Ok(lsp::CompletionItem {
18214                        label: "method id()".to_string(),
18215                        filter_text: Some("id".to_string()),
18216                        detail: Some("Now resolved!".to_string()),
18217                        documentation: Some(lsp::Documentation::String("Docs".to_string())),
18218                        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18219                            range: lsp::Range::new(
18220                                lsp::Position::new(0, 22),
18221                                lsp::Position::new(0, 22),
18222                            ),
18223                            new_text: ".id".to_string(),
18224                        })),
18225                        ..lsp::CompletionItem::default()
18226                    })
18227                } else {
18228                    Ok(item_to_resolve)
18229                }
18230            }
18231        }
18232    })
18233    .next()
18234    .await
18235    .unwrap();
18236    cx.run_until_parked();
18237
18238    cx.update_editor(|editor, window, cx| {
18239        editor.context_menu_next(&Default::default(), window, cx);
18240    });
18241
18242    cx.update_editor(|editor, _, _| {
18243        let context_menu = editor.context_menu.borrow_mut();
18244        let context_menu = context_menu
18245            .as_ref()
18246            .expect("Should have the context menu deployed");
18247        match context_menu {
18248            CodeContextMenu::Completions(completions_menu) => {
18249                let completions = completions_menu.completions.borrow_mut();
18250                assert_eq!(
18251                    completions
18252                        .iter()
18253                        .map(|completion| &completion.label.text)
18254                        .collect::<Vec<_>>(),
18255                    vec!["method id() Now resolved!", "other"],
18256                    "Should update first completion label, but not second as the filter text did not match."
18257                );
18258            }
18259            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18260        }
18261    });
18262}
18263
18264#[gpui::test]
18265async fn test_context_menus_hide_hover_popover(cx: &mut gpui::TestAppContext) {
18266    init_test(cx, |_| {});
18267    let mut cx = EditorLspTestContext::new_rust(
18268        lsp::ServerCapabilities {
18269            hover_provider: Some(lsp::HoverProviderCapability::Simple(true)),
18270            code_action_provider: Some(lsp::CodeActionProviderCapability::Simple(true)),
18271            completion_provider: Some(lsp::CompletionOptions {
18272                resolve_provider: Some(true),
18273                ..Default::default()
18274            }),
18275            ..Default::default()
18276        },
18277        cx,
18278    )
18279    .await;
18280    cx.set_state(indoc! {"
18281        struct TestStruct {
18282            field: i32
18283        }
18284
18285        fn mainˇ() {
18286            let unused_var = 42;
18287            let test_struct = TestStruct { field: 42 };
18288        }
18289    "});
18290    let symbol_range = cx.lsp_range(indoc! {"
18291        struct TestStruct {
18292            field: i32
18293        }
18294
18295        «fn main»() {
18296            let unused_var = 42;
18297            let test_struct = TestStruct { field: 42 };
18298        }
18299    "});
18300    let mut hover_requests =
18301        cx.set_request_handler::<lsp::request::HoverRequest, _, _>(move |_, _, _| async move {
18302            Ok(Some(lsp::Hover {
18303                contents: lsp::HoverContents::Markup(lsp::MarkupContent {
18304                    kind: lsp::MarkupKind::Markdown,
18305                    value: "Function documentation".to_string(),
18306                }),
18307                range: Some(symbol_range),
18308            }))
18309        });
18310
18311    // Case 1: Test that code action menu hide hover popover
18312    cx.dispatch_action(Hover);
18313    hover_requests.next().await;
18314    cx.condition(|editor, _| editor.hover_state.visible()).await;
18315    let mut code_action_requests = cx.set_request_handler::<lsp::request::CodeActionRequest, _, _>(
18316        move |_, _, _| async move {
18317            Ok(Some(vec![lsp::CodeActionOrCommand::CodeAction(
18318                lsp::CodeAction {
18319                    title: "Remove unused variable".to_string(),
18320                    kind: Some(CodeActionKind::QUICKFIX),
18321                    edit: Some(lsp::WorkspaceEdit {
18322                        changes: Some(
18323                            [(
18324                                lsp::Uri::from_file_path(path!("/file.rs")).unwrap(),
18325                                vec![lsp::TextEdit {
18326                                    range: lsp::Range::new(
18327                                        lsp::Position::new(5, 4),
18328                                        lsp::Position::new(5, 27),
18329                                    ),
18330                                    new_text: "".to_string(),
18331                                }],
18332                            )]
18333                            .into_iter()
18334                            .collect(),
18335                        ),
18336                        ..Default::default()
18337                    }),
18338                    ..Default::default()
18339                },
18340            )]))
18341        },
18342    );
18343    cx.update_editor(|editor, window, cx| {
18344        editor.toggle_code_actions(
18345            &ToggleCodeActions {
18346                deployed_from: None,
18347                quick_launch: false,
18348            },
18349            window,
18350            cx,
18351        );
18352    });
18353    code_action_requests.next().await;
18354    cx.run_until_parked();
18355    cx.condition(|editor, _| editor.context_menu_visible())
18356        .await;
18357    cx.update_editor(|editor, _, _| {
18358        assert!(
18359            !editor.hover_state.visible(),
18360            "Hover popover should be hidden when code action menu is shown"
18361        );
18362        // Hide code actions
18363        editor.context_menu.take();
18364    });
18365
18366    // Case 2: Test that code completions hide hover popover
18367    cx.dispatch_action(Hover);
18368    hover_requests.next().await;
18369    cx.condition(|editor, _| editor.hover_state.visible()).await;
18370    let counter = Arc::new(AtomicUsize::new(0));
18371    let mut completion_requests =
18372        cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18373            let counter = counter.clone();
18374            async move {
18375                counter.fetch_add(1, atomic::Ordering::Release);
18376                Ok(Some(lsp::CompletionResponse::Array(vec![
18377                    lsp::CompletionItem {
18378                        label: "main".into(),
18379                        kind: Some(lsp::CompletionItemKind::FUNCTION),
18380                        detail: Some("() -> ()".to_string()),
18381                        ..Default::default()
18382                    },
18383                    lsp::CompletionItem {
18384                        label: "TestStruct".into(),
18385                        kind: Some(lsp::CompletionItemKind::STRUCT),
18386                        detail: Some("struct TestStruct".to_string()),
18387                        ..Default::default()
18388                    },
18389                ])))
18390            }
18391        });
18392    cx.update_editor(|editor, window, cx| {
18393        editor.show_completions(&ShowCompletions, window, cx);
18394    });
18395    completion_requests.next().await;
18396    cx.condition(|editor, _| editor.context_menu_visible())
18397        .await;
18398    cx.update_editor(|editor, _, _| {
18399        assert!(
18400            !editor.hover_state.visible(),
18401            "Hover popover should be hidden when completion menu is shown"
18402        );
18403    });
18404}
18405
18406#[gpui::test]
18407async fn test_completions_resolve_happens_once(cx: &mut TestAppContext) {
18408    init_test(cx, |_| {});
18409
18410    let mut cx = EditorLspTestContext::new_rust(
18411        lsp::ServerCapabilities {
18412            completion_provider: Some(lsp::CompletionOptions {
18413                trigger_characters: Some(vec![".".to_string()]),
18414                resolve_provider: Some(true),
18415                ..Default::default()
18416            }),
18417            ..Default::default()
18418        },
18419        cx,
18420    )
18421    .await;
18422
18423    cx.set_state("fn main() { let a = 2ˇ; }");
18424    cx.simulate_keystroke(".");
18425
18426    let unresolved_item_1 = lsp::CompletionItem {
18427        label: "id".to_string(),
18428        filter_text: Some("id".to_string()),
18429        detail: None,
18430        documentation: None,
18431        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18432            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18433            new_text: ".id".to_string(),
18434        })),
18435        ..lsp::CompletionItem::default()
18436    };
18437    let resolved_item_1 = lsp::CompletionItem {
18438        additional_text_edits: Some(vec![lsp::TextEdit {
18439            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18440            new_text: "!!".to_string(),
18441        }]),
18442        ..unresolved_item_1.clone()
18443    };
18444    let unresolved_item_2 = lsp::CompletionItem {
18445        label: "other".to_string(),
18446        filter_text: Some("other".to_string()),
18447        detail: None,
18448        documentation: None,
18449        text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
18450            range: lsp::Range::new(lsp::Position::new(0, 22), lsp::Position::new(0, 22)),
18451            new_text: ".other".to_string(),
18452        })),
18453        ..lsp::CompletionItem::default()
18454    };
18455    let resolved_item_2 = lsp::CompletionItem {
18456        additional_text_edits: Some(vec![lsp::TextEdit {
18457            range: lsp::Range::new(lsp::Position::new(0, 20), lsp::Position::new(0, 22)),
18458            new_text: "??".to_string(),
18459        }]),
18460        ..unresolved_item_2.clone()
18461    };
18462
18463    let resolve_requests_1 = Arc::new(AtomicUsize::new(0));
18464    let resolve_requests_2 = Arc::new(AtomicUsize::new(0));
18465    cx.lsp
18466        .server
18467        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18468            let unresolved_item_1 = unresolved_item_1.clone();
18469            let resolved_item_1 = resolved_item_1.clone();
18470            let unresolved_item_2 = unresolved_item_2.clone();
18471            let resolved_item_2 = resolved_item_2.clone();
18472            let resolve_requests_1 = resolve_requests_1.clone();
18473            let resolve_requests_2 = resolve_requests_2.clone();
18474            move |unresolved_request, _| {
18475                let unresolved_item_1 = unresolved_item_1.clone();
18476                let resolved_item_1 = resolved_item_1.clone();
18477                let unresolved_item_2 = unresolved_item_2.clone();
18478                let resolved_item_2 = resolved_item_2.clone();
18479                let resolve_requests_1 = resolve_requests_1.clone();
18480                let resolve_requests_2 = resolve_requests_2.clone();
18481                async move {
18482                    if unresolved_request == unresolved_item_1 {
18483                        resolve_requests_1.fetch_add(1, atomic::Ordering::Release);
18484                        Ok(resolved_item_1.clone())
18485                    } else if unresolved_request == unresolved_item_2 {
18486                        resolve_requests_2.fetch_add(1, atomic::Ordering::Release);
18487                        Ok(resolved_item_2.clone())
18488                    } else {
18489                        panic!("Unexpected completion item {unresolved_request:?}")
18490                    }
18491                }
18492            }
18493        })
18494        .detach();
18495
18496    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18497        let unresolved_item_1 = unresolved_item_1.clone();
18498        let unresolved_item_2 = unresolved_item_2.clone();
18499        async move {
18500            Ok(Some(lsp::CompletionResponse::Array(vec![
18501                unresolved_item_1,
18502                unresolved_item_2,
18503            ])))
18504        }
18505    })
18506    .next()
18507    .await;
18508
18509    cx.condition(|editor, _| editor.context_menu_visible())
18510        .await;
18511    cx.update_editor(|editor, _, _| {
18512        let context_menu = editor.context_menu.borrow_mut();
18513        let context_menu = context_menu
18514            .as_ref()
18515            .expect("Should have the context menu deployed");
18516        match context_menu {
18517            CodeContextMenu::Completions(completions_menu) => {
18518                let completions = completions_menu.completions.borrow_mut();
18519                assert_eq!(
18520                    completions
18521                        .iter()
18522                        .map(|completion| &completion.label.text)
18523                        .collect::<Vec<_>>(),
18524                    vec!["id", "other"]
18525                )
18526            }
18527            CodeContextMenu::CodeActions(_) => panic!("Should show the completions menu"),
18528        }
18529    });
18530    cx.run_until_parked();
18531
18532    cx.update_editor(|editor, window, cx| {
18533        editor.context_menu_next(&ContextMenuNext, window, cx);
18534    });
18535    cx.run_until_parked();
18536    cx.update_editor(|editor, window, cx| {
18537        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18538    });
18539    cx.run_until_parked();
18540    cx.update_editor(|editor, window, cx| {
18541        editor.context_menu_next(&ContextMenuNext, window, cx);
18542    });
18543    cx.run_until_parked();
18544    cx.update_editor(|editor, window, cx| {
18545        editor
18546            .compose_completion(&ComposeCompletion::default(), window, cx)
18547            .expect("No task returned")
18548    })
18549    .await
18550    .expect("Completion failed");
18551    cx.run_until_parked();
18552
18553    cx.update_editor(|editor, _, cx| {
18554        assert_eq!(
18555            resolve_requests_1.load(atomic::Ordering::Acquire),
18556            1,
18557            "Should always resolve once despite multiple selections"
18558        );
18559        assert_eq!(
18560            resolve_requests_2.load(atomic::Ordering::Acquire),
18561            1,
18562            "Should always resolve once after multiple selections and applying the completion"
18563        );
18564        assert_eq!(
18565            editor.text(cx),
18566            "fn main() { let a = ??.other; }",
18567            "Should use resolved data when applying the completion"
18568        );
18569    });
18570}
18571
18572#[gpui::test]
18573async fn test_completions_default_resolve_data_handling(cx: &mut TestAppContext) {
18574    init_test(cx, |_| {});
18575
18576    let item_0 = lsp::CompletionItem {
18577        label: "abs".into(),
18578        insert_text: Some("abs".into()),
18579        data: Some(json!({ "very": "special"})),
18580        insert_text_mode: Some(lsp::InsertTextMode::ADJUST_INDENTATION),
18581        text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
18582            lsp::InsertReplaceEdit {
18583                new_text: "abs".to_string(),
18584                insert: lsp::Range::default(),
18585                replace: lsp::Range::default(),
18586            },
18587        )),
18588        ..lsp::CompletionItem::default()
18589    };
18590    let items = iter::once(item_0.clone())
18591        .chain((11..51).map(|i| lsp::CompletionItem {
18592            label: format!("item_{}", i),
18593            insert_text: Some(format!("item_{}", i)),
18594            insert_text_format: Some(lsp::InsertTextFormat::PLAIN_TEXT),
18595            ..lsp::CompletionItem::default()
18596        }))
18597        .collect::<Vec<_>>();
18598
18599    let default_commit_characters = vec!["?".to_string()];
18600    let default_data = json!({ "default": "data"});
18601    let default_insert_text_format = lsp::InsertTextFormat::SNIPPET;
18602    let default_insert_text_mode = lsp::InsertTextMode::AS_IS;
18603    let default_edit_range = lsp::Range {
18604        start: lsp::Position {
18605            line: 0,
18606            character: 5,
18607        },
18608        end: lsp::Position {
18609            line: 0,
18610            character: 5,
18611        },
18612    };
18613
18614    let mut cx = EditorLspTestContext::new_rust(
18615        lsp::ServerCapabilities {
18616            completion_provider: Some(lsp::CompletionOptions {
18617                trigger_characters: Some(vec![".".to_string()]),
18618                resolve_provider: Some(true),
18619                ..Default::default()
18620            }),
18621            ..Default::default()
18622        },
18623        cx,
18624    )
18625    .await;
18626
18627    cx.set_state("fn main() { let a = 2ˇ; }");
18628    cx.simulate_keystroke(".");
18629
18630    let completion_data = default_data.clone();
18631    let completion_characters = default_commit_characters.clone();
18632    let completion_items = items.clone();
18633    cx.set_request_handler::<lsp::request::Completion, _, _>(move |_, _, _| {
18634        let default_data = completion_data.clone();
18635        let default_commit_characters = completion_characters.clone();
18636        let items = completion_items.clone();
18637        async move {
18638            Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
18639                items,
18640                item_defaults: Some(lsp::CompletionListItemDefaults {
18641                    data: Some(default_data.clone()),
18642                    commit_characters: Some(default_commit_characters.clone()),
18643                    edit_range: Some(lsp::CompletionListItemDefaultsEditRange::Range(
18644                        default_edit_range,
18645                    )),
18646                    insert_text_format: Some(default_insert_text_format),
18647                    insert_text_mode: Some(default_insert_text_mode),
18648                }),
18649                ..lsp::CompletionList::default()
18650            })))
18651        }
18652    })
18653    .next()
18654    .await;
18655
18656    let resolved_items = Arc::new(Mutex::new(Vec::new()));
18657    cx.lsp
18658        .server
18659        .on_request::<lsp::request::ResolveCompletionItem, _, _>({
18660            let closure_resolved_items = resolved_items.clone();
18661            move |item_to_resolve, _| {
18662                let closure_resolved_items = closure_resolved_items.clone();
18663                async move {
18664                    closure_resolved_items.lock().push(item_to_resolve.clone());
18665                    Ok(item_to_resolve)
18666                }
18667            }
18668        })
18669        .detach();
18670
18671    cx.condition(|editor, _| editor.context_menu_visible())
18672        .await;
18673    cx.run_until_parked();
18674    cx.update_editor(|editor, _, _| {
18675        let menu = editor.context_menu.borrow_mut();
18676        match menu.as_ref().expect("should have the completions menu") {
18677            CodeContextMenu::Completions(completions_menu) => {
18678                assert_eq!(
18679                    completions_menu
18680                        .entries
18681                        .borrow()
18682                        .iter()
18683                        .map(|mat| mat.string.clone())
18684                        .collect::<Vec<String>>(),
18685                    items
18686                        .iter()
18687                        .map(|completion| completion.label.clone())
18688                        .collect::<Vec<String>>()
18689                );
18690            }
18691            CodeContextMenu::CodeActions(_) => panic!("Expected to have the completions menu"),
18692        }
18693    });
18694    // Approximate initial displayed interval is 0..12. With extra item padding of 4 this is 0..16
18695    // with 4 from the end.
18696    assert_eq!(
18697        *resolved_items.lock(),
18698        [&items[0..16], &items[items.len() - 4..items.len()]]
18699            .concat()
18700            .iter()
18701            .cloned()
18702            .map(|mut item| {
18703                if item.data.is_none() {
18704                    item.data = Some(default_data.clone());
18705                }
18706                item
18707            })
18708            .collect::<Vec<lsp::CompletionItem>>(),
18709        "Items sent for resolve should be unchanged modulo resolve `data` filled with default if missing"
18710    );
18711    resolved_items.lock().clear();
18712
18713    cx.update_editor(|editor, window, cx| {
18714        editor.context_menu_prev(&ContextMenuPrevious, window, cx);
18715    });
18716    cx.run_until_parked();
18717    // Completions that have already been resolved are skipped.
18718    assert_eq!(
18719        *resolved_items.lock(),
18720        items[items.len() - 17..items.len() - 4]
18721            .iter()
18722            .cloned()
18723            .map(|mut item| {
18724                if item.data.is_none() {
18725                    item.data = Some(default_data.clone());
18726                }
18727                item
18728            })
18729            .collect::<Vec<lsp::CompletionItem>>()
18730    );
18731    resolved_items.lock().clear();
18732}
18733
18734#[gpui::test]
18735async fn test_completions_in_languages_with_extra_word_characters(cx: &mut TestAppContext) {
18736    init_test(cx, |_| {});
18737
18738    let mut cx = EditorLspTestContext::new(
18739        Language::new(
18740            LanguageConfig {
18741                matcher: LanguageMatcher {
18742                    path_suffixes: vec!["jsx".into()],
18743                    ..Default::default()
18744                },
18745                overrides: [(
18746                    "element".into(),
18747                    LanguageConfigOverride {
18748                        completion_query_characters: Override::Set(['-'].into_iter().collect()),
18749                        ..Default::default()
18750                    },
18751                )]
18752                .into_iter()
18753                .collect(),
18754                ..Default::default()
18755            },
18756            Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
18757        )
18758        .with_override_query("(jsx_self_closing_element) @element")
18759        .unwrap(),
18760        lsp::ServerCapabilities {
18761            completion_provider: Some(lsp::CompletionOptions {
18762                trigger_characters: Some(vec![":".to_string()]),
18763                ..Default::default()
18764            }),
18765            ..Default::default()
18766        },
18767        cx,
18768    )
18769    .await;
18770
18771    cx.lsp
18772        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
18773            Ok(Some(lsp::CompletionResponse::Array(vec![
18774                lsp::CompletionItem {
18775                    label: "bg-blue".into(),
18776                    ..Default::default()
18777                },
18778                lsp::CompletionItem {
18779                    label: "bg-red".into(),
18780                    ..Default::default()
18781                },
18782                lsp::CompletionItem {
18783                    label: "bg-yellow".into(),
18784                    ..Default::default()
18785                },
18786            ])))
18787        });
18788
18789    cx.set_state(r#"<p class="bgˇ" />"#);
18790
18791    // Trigger completion when typing a dash, because the dash is an extra
18792    // word character in the 'element' scope, which contains the cursor.
18793    cx.simulate_keystroke("-");
18794    cx.executor().run_until_parked();
18795    cx.update_editor(|editor, _, _| {
18796        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18797        {
18798            assert_eq!(
18799                completion_menu_entries(menu),
18800                &["bg-blue", "bg-red", "bg-yellow"]
18801            );
18802        } else {
18803            panic!("expected completion menu to be open");
18804        }
18805    });
18806
18807    cx.simulate_keystroke("l");
18808    cx.executor().run_until_parked();
18809    cx.update_editor(|editor, _, _| {
18810        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18811        {
18812            assert_eq!(completion_menu_entries(menu), &["bg-blue", "bg-yellow"]);
18813        } else {
18814            panic!("expected completion menu to be open");
18815        }
18816    });
18817
18818    // When filtering completions, consider the character after the '-' to
18819    // be the start of a subword.
18820    cx.set_state(r#"<p class="yelˇ" />"#);
18821    cx.simulate_keystroke("l");
18822    cx.executor().run_until_parked();
18823    cx.update_editor(|editor, _, _| {
18824        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow_mut().as_ref()
18825        {
18826            assert_eq!(completion_menu_entries(menu), &["bg-yellow"]);
18827        } else {
18828            panic!("expected completion menu to be open");
18829        }
18830    });
18831}
18832
18833fn completion_menu_entries(menu: &CompletionsMenu) -> Vec<String> {
18834    let entries = menu.entries.borrow();
18835    entries.iter().map(|mat| mat.string.clone()).collect()
18836}
18837
18838#[gpui::test]
18839async fn test_document_format_with_prettier(cx: &mut TestAppContext) {
18840    init_test(cx, |settings| {
18841        settings.defaults.formatter = Some(FormatterList::Single(Formatter::Prettier))
18842    });
18843
18844    let fs = FakeFs::new(cx.executor());
18845    fs.insert_file(path!("/file.ts"), Default::default()).await;
18846
18847    let project = Project::test(fs, [path!("/file.ts").as_ref()], cx).await;
18848    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
18849
18850    language_registry.add(Arc::new(Language::new(
18851        LanguageConfig {
18852            name: "TypeScript".into(),
18853            matcher: LanguageMatcher {
18854                path_suffixes: vec!["ts".to_string()],
18855                ..Default::default()
18856            },
18857            ..Default::default()
18858        },
18859        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
18860    )));
18861    update_test_language_settings(cx, |settings| {
18862        settings.defaults.prettier.get_or_insert_default().allowed = Some(true);
18863    });
18864
18865    let test_plugin = "test_plugin";
18866    let _ = language_registry.register_fake_lsp(
18867        "TypeScript",
18868        FakeLspAdapter {
18869            prettier_plugins: vec![test_plugin],
18870            ..Default::default()
18871        },
18872    );
18873
18874    let prettier_format_suffix = project::TEST_PRETTIER_FORMAT_SUFFIX;
18875    let buffer = project
18876        .update(cx, |project, cx| {
18877            project.open_local_buffer(path!("/file.ts"), cx)
18878        })
18879        .await
18880        .unwrap();
18881
18882    let buffer_text = "one\ntwo\nthree\n";
18883    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
18884    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
18885    editor.update_in(cx, |editor, window, cx| {
18886        editor.set_text(buffer_text, window, cx)
18887    });
18888
18889    editor
18890        .update_in(cx, |editor, window, cx| {
18891            editor.perform_format(
18892                project.clone(),
18893                FormatTrigger::Manual,
18894                FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18895                window,
18896                cx,
18897            )
18898        })
18899        .unwrap()
18900        .await;
18901    assert_eq!(
18902        editor.update(cx, |editor, cx| editor.text(cx)),
18903        buffer_text.to_string() + prettier_format_suffix,
18904        "Test prettier formatting was not applied to the original buffer text",
18905    );
18906
18907    update_test_language_settings(cx, |settings| {
18908        settings.defaults.formatter = Some(FormatterList::default())
18909    });
18910    let format = editor.update_in(cx, |editor, window, cx| {
18911        editor.perform_format(
18912            project.clone(),
18913            FormatTrigger::Manual,
18914            FormatTarget::Buffers(editor.buffer().read(cx).all_buffers()),
18915            window,
18916            cx,
18917        )
18918    });
18919    format.await.unwrap();
18920    assert_eq!(
18921        editor.update(cx, |editor, cx| editor.text(cx)),
18922        buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,
18923        "Autoformatting (via test prettier) was not applied to the original buffer text",
18924    );
18925}
18926
18927#[gpui::test]
18928async fn test_addition_reverts(cx: &mut TestAppContext) {
18929    init_test(cx, |_| {});
18930    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
18931    let base_text = indoc! {r#"
18932        struct Row;
18933        struct Row1;
18934        struct Row2;
18935
18936        struct Row4;
18937        struct Row5;
18938        struct Row6;
18939
18940        struct Row8;
18941        struct Row9;
18942        struct Row10;"#};
18943
18944    // When addition hunks are not adjacent to carets, no hunk revert is performed
18945    assert_hunk_revert(
18946        indoc! {r#"struct Row;
18947                   struct Row1;
18948                   struct Row1.1;
18949                   struct Row1.2;
18950                   struct Row2;ˇ
18951
18952                   struct Row4;
18953                   struct Row5;
18954                   struct Row6;
18955
18956                   struct Row8;
18957                   ˇstruct Row9;
18958                   struct Row9.1;
18959                   struct Row9.2;
18960                   struct Row9.3;
18961                   struct Row10;"#},
18962        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
18963        indoc! {r#"struct Row;
18964                   struct Row1;
18965                   struct Row1.1;
18966                   struct Row1.2;
18967                   struct Row2;ˇ
18968
18969                   struct Row4;
18970                   struct Row5;
18971                   struct Row6;
18972
18973                   struct Row8;
18974                   ˇstruct Row9;
18975                   struct Row9.1;
18976                   struct Row9.2;
18977                   struct Row9.3;
18978                   struct Row10;"#},
18979        base_text,
18980        &mut cx,
18981    );
18982    // Same for selections
18983    assert_hunk_revert(
18984        indoc! {r#"struct Row;
18985                   struct Row1;
18986                   struct Row2;
18987                   struct Row2.1;
18988                   struct Row2.2;
18989                   «ˇ
18990                   struct Row4;
18991                   struct» Row5;
18992                   «struct Row6;
18993                   ˇ»
18994                   struct Row9.1;
18995                   struct Row9.2;
18996                   struct Row9.3;
18997                   struct Row8;
18998                   struct Row9;
18999                   struct Row10;"#},
19000        vec![DiffHunkStatusKind::Added, DiffHunkStatusKind::Added],
19001        indoc! {r#"struct Row;
19002                   struct Row1;
19003                   struct Row2;
19004                   struct Row2.1;
19005                   struct Row2.2;
19006                   «ˇ
19007                   struct Row4;
19008                   struct» Row5;
19009                   «struct Row6;
19010                   ˇ»
19011                   struct Row9.1;
19012                   struct Row9.2;
19013                   struct Row9.3;
19014                   struct Row8;
19015                   struct Row9;
19016                   struct Row10;"#},
19017        base_text,
19018        &mut cx,
19019    );
19020
19021    // When carets and selections intersect the addition hunks, those are reverted.
19022    // Adjacent carets got merged.
19023    assert_hunk_revert(
19024        indoc! {r#"struct Row;
19025                   ˇ// something on the top
19026                   struct Row1;
19027                   struct Row2;
19028                   struct Roˇw3.1;
19029                   struct Row2.2;
19030                   struct Row2.3;ˇ
19031
19032                   struct Row4;
19033                   struct ˇRow5.1;
19034                   struct Row5.2;
19035                   struct «Rowˇ»5.3;
19036                   struct Row5;
19037                   struct Row6;
19038                   ˇ
19039                   struct Row9.1;
19040                   struct «Rowˇ»9.2;
19041                   struct «ˇRow»9.3;
19042                   struct Row8;
19043                   struct Row9;
19044                   «ˇ// something on bottom»
19045                   struct Row10;"#},
19046        vec![
19047            DiffHunkStatusKind::Added,
19048            DiffHunkStatusKind::Added,
19049            DiffHunkStatusKind::Added,
19050            DiffHunkStatusKind::Added,
19051            DiffHunkStatusKind::Added,
19052        ],
19053        indoc! {r#"struct Row;
19054                   ˇstruct Row1;
19055                   struct Row2;
19056                   ˇ
19057                   struct Row4;
19058                   ˇstruct Row5;
19059                   struct Row6;
19060                   ˇ
19061                   ˇstruct Row8;
19062                   struct Row9;
19063                   ˇstruct Row10;"#},
19064        base_text,
19065        &mut cx,
19066    );
19067}
19068
19069#[gpui::test]
19070async fn test_modification_reverts(cx: &mut TestAppContext) {
19071    init_test(cx, |_| {});
19072    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19073    let base_text = indoc! {r#"
19074        struct Row;
19075        struct Row1;
19076        struct Row2;
19077
19078        struct Row4;
19079        struct Row5;
19080        struct Row6;
19081
19082        struct Row8;
19083        struct Row9;
19084        struct Row10;"#};
19085
19086    // Modification hunks behave the same as the addition ones.
19087    assert_hunk_revert(
19088        indoc! {r#"struct Row;
19089                   struct Row1;
19090                   struct Row33;
19091                   ˇ
19092                   struct Row4;
19093                   struct Row5;
19094                   struct Row6;
19095                   ˇ
19096                   struct Row99;
19097                   struct Row9;
19098                   struct Row10;"#},
19099        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19100        indoc! {r#"struct Row;
19101                   struct Row1;
19102                   struct Row33;
19103                   ˇ
19104                   struct Row4;
19105                   struct Row5;
19106                   struct Row6;
19107                   ˇ
19108                   struct Row99;
19109                   struct Row9;
19110                   struct Row10;"#},
19111        base_text,
19112        &mut cx,
19113    );
19114    assert_hunk_revert(
19115        indoc! {r#"struct Row;
19116                   struct Row1;
19117                   struct Row33;
19118                   «ˇ
19119                   struct Row4;
19120                   struct» Row5;
19121                   «struct Row6;
19122                   ˇ»
19123                   struct Row99;
19124                   struct Row9;
19125                   struct Row10;"#},
19126        vec![DiffHunkStatusKind::Modified, DiffHunkStatusKind::Modified],
19127        indoc! {r#"struct Row;
19128                   struct Row1;
19129                   struct Row33;
19130                   «ˇ
19131                   struct Row4;
19132                   struct» Row5;
19133                   «struct Row6;
19134                   ˇ»
19135                   struct Row99;
19136                   struct Row9;
19137                   struct Row10;"#},
19138        base_text,
19139        &mut cx,
19140    );
19141
19142    assert_hunk_revert(
19143        indoc! {r#"ˇstruct Row1.1;
19144                   struct Row1;
19145                   «ˇstr»uct Row22;
19146
19147                   struct ˇRow44;
19148                   struct Row5;
19149                   struct «Rˇ»ow66;ˇ
19150
19151                   «struˇ»ct Row88;
19152                   struct Row9;
19153                   struct Row1011;ˇ"#},
19154        vec![
19155            DiffHunkStatusKind::Modified,
19156            DiffHunkStatusKind::Modified,
19157            DiffHunkStatusKind::Modified,
19158            DiffHunkStatusKind::Modified,
19159            DiffHunkStatusKind::Modified,
19160            DiffHunkStatusKind::Modified,
19161        ],
19162        indoc! {r#"struct Row;
19163                   ˇstruct Row1;
19164                   struct Row2;
19165                   ˇ
19166                   struct Row4;
19167                   ˇstruct Row5;
19168                   struct Row6;
19169                   ˇ
19170                   struct Row8;
19171                   ˇstruct Row9;
19172                   struct Row10;ˇ"#},
19173        base_text,
19174        &mut cx,
19175    );
19176}
19177
19178#[gpui::test]
19179async fn test_deleting_over_diff_hunk(cx: &mut TestAppContext) {
19180    init_test(cx, |_| {});
19181    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19182    let base_text = indoc! {r#"
19183        one
19184
19185        two
19186        three
19187        "#};
19188
19189    cx.set_head_text(base_text);
19190    cx.set_state("\nˇ\n");
19191    cx.executor().run_until_parked();
19192    cx.update_editor(|editor, _window, cx| {
19193        editor.expand_selected_diff_hunks(cx);
19194    });
19195    cx.executor().run_until_parked();
19196    cx.update_editor(|editor, window, cx| {
19197        editor.backspace(&Default::default(), window, cx);
19198    });
19199    cx.run_until_parked();
19200    cx.assert_state_with_diff(
19201        indoc! {r#"
19202
19203        - two
19204        - threeˇ
19205        +
19206        "#}
19207        .to_string(),
19208    );
19209}
19210
19211#[gpui::test]
19212async fn test_deletion_reverts(cx: &mut TestAppContext) {
19213    init_test(cx, |_| {});
19214    let mut cx = EditorLspTestContext::new_rust(lsp::ServerCapabilities::default(), cx).await;
19215    let base_text = indoc! {r#"struct Row;
19216struct Row1;
19217struct Row2;
19218
19219struct Row4;
19220struct Row5;
19221struct Row6;
19222
19223struct Row8;
19224struct Row9;
19225struct Row10;"#};
19226
19227    // Deletion hunks trigger with carets on adjacent rows, so carets and selections have to stay farther to avoid the revert
19228    assert_hunk_revert(
19229        indoc! {r#"struct Row;
19230                   struct Row2;
19231
19232                   ˇstruct Row4;
19233                   struct Row5;
19234                   struct Row6;
19235                   ˇ
19236                   struct Row8;
19237                   struct Row10;"#},
19238        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19239        indoc! {r#"struct Row;
19240                   struct Row2;
19241
19242                   ˇstruct Row4;
19243                   struct Row5;
19244                   struct Row6;
19245                   ˇ
19246                   struct Row8;
19247                   struct Row10;"#},
19248        base_text,
19249        &mut cx,
19250    );
19251    assert_hunk_revert(
19252        indoc! {r#"struct Row;
19253                   struct Row2;
19254
19255                   «ˇstruct Row4;
19256                   struct» Row5;
19257                   «struct Row6;
19258                   ˇ»
19259                   struct Row8;
19260                   struct Row10;"#},
19261        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19262        indoc! {r#"struct Row;
19263                   struct Row2;
19264
19265                   «ˇstruct Row4;
19266                   struct» Row5;
19267                   «struct Row6;
19268                   ˇ»
19269                   struct Row8;
19270                   struct Row10;"#},
19271        base_text,
19272        &mut cx,
19273    );
19274
19275    // Deletion hunks are ephemeral, so it's impossible to place the caret into them — Zed triggers reverts for lines, adjacent to carets and selections.
19276    assert_hunk_revert(
19277        indoc! {r#"struct Row;
19278                   ˇstruct Row2;
19279
19280                   struct Row4;
19281                   struct Row5;
19282                   struct Row6;
19283
19284                   struct Row8;ˇ
19285                   struct Row10;"#},
19286        vec![DiffHunkStatusKind::Deleted, DiffHunkStatusKind::Deleted],
19287        indoc! {r#"struct Row;
19288                   struct Row1;
19289                   ˇstruct Row2;
19290
19291                   struct Row4;
19292                   struct Row5;
19293                   struct Row6;
19294
19295                   struct Row8;ˇ
19296                   struct Row9;
19297                   struct Row10;"#},
19298        base_text,
19299        &mut cx,
19300    );
19301    assert_hunk_revert(
19302        indoc! {r#"struct Row;
19303                   struct Row2«ˇ;
19304                   struct Row4;
19305                   struct» Row5;
19306                   «struct Row6;
19307
19308                   struct Row8;ˇ»
19309                   struct Row10;"#},
19310        vec![
19311            DiffHunkStatusKind::Deleted,
19312            DiffHunkStatusKind::Deleted,
19313            DiffHunkStatusKind::Deleted,
19314        ],
19315        indoc! {r#"struct Row;
19316                   struct Row1;
19317                   struct Row2«ˇ;
19318
19319                   struct Row4;
19320                   struct» Row5;
19321                   «struct Row6;
19322
19323                   struct Row8;ˇ»
19324                   struct Row9;
19325                   struct Row10;"#},
19326        base_text,
19327        &mut cx,
19328    );
19329}
19330
19331#[gpui::test]
19332async fn test_multibuffer_reverts(cx: &mut TestAppContext) {
19333    init_test(cx, |_| {});
19334
19335    let base_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj";
19336    let base_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu";
19337    let base_text_3 =
19338        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}";
19339
19340    let text_1 = edit_first_char_of_every_line(base_text_1);
19341    let text_2 = edit_first_char_of_every_line(base_text_2);
19342    let text_3 = edit_first_char_of_every_line(base_text_3);
19343
19344    let buffer_1 = cx.new(|cx| Buffer::local(text_1.clone(), cx));
19345    let buffer_2 = cx.new(|cx| Buffer::local(text_2.clone(), cx));
19346    let buffer_3 = cx.new(|cx| Buffer::local(text_3.clone(), cx));
19347
19348    let multibuffer = cx.new(|cx| {
19349        let mut multibuffer = MultiBuffer::new(ReadWrite);
19350        multibuffer.push_excerpts(
19351            buffer_1.clone(),
19352            [
19353                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19354                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19355                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19356            ],
19357            cx,
19358        );
19359        multibuffer.push_excerpts(
19360            buffer_2.clone(),
19361            [
19362                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19363                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19364                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19365            ],
19366            cx,
19367        );
19368        multibuffer.push_excerpts(
19369            buffer_3.clone(),
19370            [
19371                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19372                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19373                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19374            ],
19375            cx,
19376        );
19377        multibuffer
19378    });
19379
19380    let fs = FakeFs::new(cx.executor());
19381    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
19382    let (editor, cx) = cx
19383        .add_window_view(|window, cx| build_editor_with_project(project, multibuffer, window, cx));
19384    editor.update_in(cx, |editor, _window, cx| {
19385        for (buffer, diff_base) in [
19386            (buffer_1.clone(), base_text_1),
19387            (buffer_2.clone(), base_text_2),
19388            (buffer_3.clone(), base_text_3),
19389        ] {
19390            let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
19391            editor
19392                .buffer
19393                .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
19394        }
19395    });
19396    cx.executor().run_until_parked();
19397
19398    editor.update_in(cx, |editor, window, cx| {
19399        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}");
19400        editor.select_all(&SelectAll, window, cx);
19401        editor.git_restore(&Default::default(), window, cx);
19402    });
19403    cx.executor().run_until_parked();
19404
19405    // When all ranges are selected, all buffer hunks are reverted.
19406    editor.update(cx, |editor, cx| {
19407        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");
19408    });
19409    buffer_1.update(cx, |buffer, _| {
19410        assert_eq!(buffer.text(), base_text_1);
19411    });
19412    buffer_2.update(cx, |buffer, _| {
19413        assert_eq!(buffer.text(), base_text_2);
19414    });
19415    buffer_3.update(cx, |buffer, _| {
19416        assert_eq!(buffer.text(), base_text_3);
19417    });
19418
19419    editor.update_in(cx, |editor, window, cx| {
19420        editor.undo(&Default::default(), window, cx);
19421    });
19422
19423    editor.update_in(cx, |editor, window, cx| {
19424        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19425            s.select_ranges(Some(Point::new(0, 0)..Point::new(6, 0)));
19426        });
19427        editor.git_restore(&Default::default(), window, cx);
19428    });
19429
19430    // Now, when all ranges selected belong to buffer_1, the revert should succeed,
19431    // but not affect buffer_2 and its related excerpts.
19432    editor.update(cx, |editor, cx| {
19433        assert_eq!(
19434            editor.text(cx),
19435            "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}"
19436        );
19437    });
19438    buffer_1.update(cx, |buffer, _| {
19439        assert_eq!(buffer.text(), base_text_1);
19440    });
19441    buffer_2.update(cx, |buffer, _| {
19442        assert_eq!(
19443            buffer.text(),
19444            "Xlll\nXmmm\nXnnn\nXooo\nXppp\nXqqq\nXrrr\nXsss\nXttt\nXuuu"
19445        );
19446    });
19447    buffer_3.update(cx, |buffer, _| {
19448        assert_eq!(
19449            buffer.text(),
19450            "Xvvv\nXwww\nXxxx\nXyyy\nXzzz\nX{{{\nX|||\nX}}}\nX~~~\nX\u{7f}\u{7f}\u{7f}"
19451        );
19452    });
19453
19454    fn edit_first_char_of_every_line(text: &str) -> String {
19455        text.split('\n')
19456            .map(|line| format!("X{}", &line[1..]))
19457            .collect::<Vec<_>>()
19458            .join("\n")
19459    }
19460}
19461
19462#[gpui::test]
19463async fn test_multibuffer_in_navigation_history(cx: &mut TestAppContext) {
19464    init_test(cx, |_| {});
19465
19466    let cols = 4;
19467    let rows = 10;
19468    let sample_text_1 = sample_text(rows, cols, 'a');
19469    assert_eq!(
19470        sample_text_1,
19471        "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj"
19472    );
19473    let sample_text_2 = sample_text(rows, cols, 'l');
19474    assert_eq!(
19475        sample_text_2,
19476        "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu"
19477    );
19478    let sample_text_3 = sample_text(rows, cols, 'v');
19479    assert_eq!(
19480        sample_text_3,
19481        "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n{{{{\n||||\n}}}}\n~~~~\n\u{7f}\u{7f}\u{7f}\u{7f}"
19482    );
19483
19484    let buffer_1 = cx.new(|cx| Buffer::local(sample_text_1.clone(), cx));
19485    let buffer_2 = cx.new(|cx| Buffer::local(sample_text_2.clone(), cx));
19486    let buffer_3 = cx.new(|cx| Buffer::local(sample_text_3.clone(), cx));
19487
19488    let multi_buffer = cx.new(|cx| {
19489        let mut multibuffer = MultiBuffer::new(ReadWrite);
19490        multibuffer.push_excerpts(
19491            buffer_1.clone(),
19492            [
19493                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19494                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19495                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19496            ],
19497            cx,
19498        );
19499        multibuffer.push_excerpts(
19500            buffer_2.clone(),
19501            [
19502                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19503                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19504                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19505            ],
19506            cx,
19507        );
19508        multibuffer.push_excerpts(
19509            buffer_3.clone(),
19510            [
19511                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19512                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19513                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
19514            ],
19515            cx,
19516        );
19517        multibuffer
19518    });
19519
19520    let fs = FakeFs::new(cx.executor());
19521    fs.insert_tree(
19522        "/a",
19523        json!({
19524            "main.rs": sample_text_1,
19525            "other.rs": sample_text_2,
19526            "lib.rs": sample_text_3,
19527        }),
19528    )
19529    .await;
19530    let project = Project::test(fs, ["/a".as_ref()], cx).await;
19531    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
19532    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
19533    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
19534        Editor::new(
19535            EditorMode::full(),
19536            multi_buffer,
19537            Some(project.clone()),
19538            window,
19539            cx,
19540        )
19541    });
19542    let multibuffer_item_id = workspace
19543        .update(cx, |workspace, window, cx| {
19544            assert!(
19545                workspace.active_item(cx).is_none(),
19546                "active item should be None before the first item is added"
19547            );
19548            workspace.add_item_to_active_pane(
19549                Box::new(multi_buffer_editor.clone()),
19550                None,
19551                true,
19552                window,
19553                cx,
19554            );
19555            let active_item = workspace
19556                .active_item(cx)
19557                .expect("should have an active item after adding the multi buffer");
19558            assert_eq!(
19559                active_item.buffer_kind(cx),
19560                ItemBufferKind::Multibuffer,
19561                "A multi buffer was expected to active after adding"
19562            );
19563            active_item.item_id()
19564        })
19565        .unwrap();
19566    cx.executor().run_until_parked();
19567
19568    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19569        editor.change_selections(
19570            SelectionEffects::scroll(Autoscroll::Next),
19571            window,
19572            cx,
19573            |s| s.select_ranges(Some(MultiBufferOffset(1)..MultiBufferOffset(2))),
19574        );
19575        editor.open_excerpts(&OpenExcerpts, window, cx);
19576    });
19577    cx.executor().run_until_parked();
19578    let first_item_id = workspace
19579        .update(cx, |workspace, window, cx| {
19580            let active_item = workspace
19581                .active_item(cx)
19582                .expect("should have an active item after navigating into the 1st buffer");
19583            let first_item_id = active_item.item_id();
19584            assert_ne!(
19585                first_item_id, multibuffer_item_id,
19586                "Should navigate into the 1st buffer and activate it"
19587            );
19588            assert_eq!(
19589                active_item.buffer_kind(cx),
19590                ItemBufferKind::Singleton,
19591                "New active item should be a singleton buffer"
19592            );
19593            assert_eq!(
19594                active_item
19595                    .act_as::<Editor>(cx)
19596                    .expect("should have navigated into an editor for the 1st buffer")
19597                    .read(cx)
19598                    .text(cx),
19599                sample_text_1
19600            );
19601
19602            workspace
19603                .go_back(workspace.active_pane().downgrade(), window, cx)
19604                .detach_and_log_err(cx);
19605
19606            first_item_id
19607        })
19608        .unwrap();
19609    cx.executor().run_until_parked();
19610    workspace
19611        .update(cx, |workspace, _, cx| {
19612            let active_item = workspace
19613                .active_item(cx)
19614                .expect("should have an active item after navigating back");
19615            assert_eq!(
19616                active_item.item_id(),
19617                multibuffer_item_id,
19618                "Should navigate back to the multi buffer"
19619            );
19620            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19621        })
19622        .unwrap();
19623
19624    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19625        editor.change_selections(
19626            SelectionEffects::scroll(Autoscroll::Next),
19627            window,
19628            cx,
19629            |s| s.select_ranges(Some(MultiBufferOffset(39)..MultiBufferOffset(40))),
19630        );
19631        editor.open_excerpts(&OpenExcerpts, window, cx);
19632    });
19633    cx.executor().run_until_parked();
19634    let second_item_id = workspace
19635        .update(cx, |workspace, window, cx| {
19636            let active_item = workspace
19637                .active_item(cx)
19638                .expect("should have an active item after navigating into the 2nd buffer");
19639            let second_item_id = active_item.item_id();
19640            assert_ne!(
19641                second_item_id, multibuffer_item_id,
19642                "Should navigate away from the multibuffer"
19643            );
19644            assert_ne!(
19645                second_item_id, first_item_id,
19646                "Should navigate into the 2nd buffer and activate it"
19647            );
19648            assert_eq!(
19649                active_item.buffer_kind(cx),
19650                ItemBufferKind::Singleton,
19651                "New active item should be a singleton buffer"
19652            );
19653            assert_eq!(
19654                active_item
19655                    .act_as::<Editor>(cx)
19656                    .expect("should have navigated into an editor")
19657                    .read(cx)
19658                    .text(cx),
19659                sample_text_2
19660            );
19661
19662            workspace
19663                .go_back(workspace.active_pane().downgrade(), window, cx)
19664                .detach_and_log_err(cx);
19665
19666            second_item_id
19667        })
19668        .unwrap();
19669    cx.executor().run_until_parked();
19670    workspace
19671        .update(cx, |workspace, _, cx| {
19672            let active_item = workspace
19673                .active_item(cx)
19674                .expect("should have an active item after navigating back from the 2nd buffer");
19675            assert_eq!(
19676                active_item.item_id(),
19677                multibuffer_item_id,
19678                "Should navigate back from the 2nd buffer to the multi buffer"
19679            );
19680            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19681        })
19682        .unwrap();
19683
19684    multi_buffer_editor.update_in(cx, |editor, window, cx| {
19685        editor.change_selections(
19686            SelectionEffects::scroll(Autoscroll::Next),
19687            window,
19688            cx,
19689            |s| s.select_ranges(Some(MultiBufferOffset(70)..MultiBufferOffset(70))),
19690        );
19691        editor.open_excerpts(&OpenExcerpts, window, cx);
19692    });
19693    cx.executor().run_until_parked();
19694    workspace
19695        .update(cx, |workspace, window, cx| {
19696            let active_item = workspace
19697                .active_item(cx)
19698                .expect("should have an active item after navigating into the 3rd buffer");
19699            let third_item_id = active_item.item_id();
19700            assert_ne!(
19701                third_item_id, multibuffer_item_id,
19702                "Should navigate into the 3rd buffer and activate it"
19703            );
19704            assert_ne!(third_item_id, first_item_id);
19705            assert_ne!(third_item_id, second_item_id);
19706            assert_eq!(
19707                active_item.buffer_kind(cx),
19708                ItemBufferKind::Singleton,
19709                "New active item should be a singleton buffer"
19710            );
19711            assert_eq!(
19712                active_item
19713                    .act_as::<Editor>(cx)
19714                    .expect("should have navigated into an editor")
19715                    .read(cx)
19716                    .text(cx),
19717                sample_text_3
19718            );
19719
19720            workspace
19721                .go_back(workspace.active_pane().downgrade(), window, cx)
19722                .detach_and_log_err(cx);
19723        })
19724        .unwrap();
19725    cx.executor().run_until_parked();
19726    workspace
19727        .update(cx, |workspace, _, cx| {
19728            let active_item = workspace
19729                .active_item(cx)
19730                .expect("should have an active item after navigating back from the 3rd buffer");
19731            assert_eq!(
19732                active_item.item_id(),
19733                multibuffer_item_id,
19734                "Should navigate back from the 3rd buffer to the multi buffer"
19735            );
19736            assert_eq!(active_item.buffer_kind(cx), ItemBufferKind::Multibuffer);
19737        })
19738        .unwrap();
19739}
19740
19741#[gpui::test]
19742async fn test_toggle_selected_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
19743    init_test(cx, |_| {});
19744
19745    let mut cx = EditorTestContext::new(cx).await;
19746
19747    let diff_base = r#"
19748        use some::mod;
19749
19750        const A: u32 = 42;
19751
19752        fn main() {
19753            println!("hello");
19754
19755            println!("world");
19756        }
19757        "#
19758    .unindent();
19759
19760    cx.set_state(
19761        &r#"
19762        use some::modified;
19763
19764        ˇ
19765        fn main() {
19766            println!("hello there");
19767
19768            println!("around the");
19769            println!("world");
19770        }
19771        "#
19772        .unindent(),
19773    );
19774
19775    cx.set_head_text(&diff_base);
19776    executor.run_until_parked();
19777
19778    cx.update_editor(|editor, window, cx| {
19779        editor.go_to_next_hunk(&GoToHunk, window, cx);
19780        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19781    });
19782    executor.run_until_parked();
19783    cx.assert_state_with_diff(
19784        r#"
19785          use some::modified;
19786
19787
19788          fn main() {
19789        -     println!("hello");
19790        + ˇ    println!("hello there");
19791
19792              println!("around the");
19793              println!("world");
19794          }
19795        "#
19796        .unindent(),
19797    );
19798
19799    cx.update_editor(|editor, window, cx| {
19800        for _ in 0..2 {
19801            editor.go_to_next_hunk(&GoToHunk, window, cx);
19802            editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19803        }
19804    });
19805    executor.run_until_parked();
19806    cx.assert_state_with_diff(
19807        r#"
19808        - use some::mod;
19809        + ˇuse some::modified;
19810
19811
19812          fn main() {
19813        -     println!("hello");
19814        +     println!("hello there");
19815
19816        +     println!("around the");
19817              println!("world");
19818          }
19819        "#
19820        .unindent(),
19821    );
19822
19823    cx.update_editor(|editor, window, cx| {
19824        editor.go_to_next_hunk(&GoToHunk, window, cx);
19825        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
19826    });
19827    executor.run_until_parked();
19828    cx.assert_state_with_diff(
19829        r#"
19830        - use some::mod;
19831        + use some::modified;
19832
19833        - const A: u32 = 42;
19834          ˇ
19835          fn main() {
19836        -     println!("hello");
19837        +     println!("hello there");
19838
19839        +     println!("around the");
19840              println!("world");
19841          }
19842        "#
19843        .unindent(),
19844    );
19845
19846    cx.update_editor(|editor, window, cx| {
19847        editor.cancel(&Cancel, window, cx);
19848    });
19849
19850    cx.assert_state_with_diff(
19851        r#"
19852          use some::modified;
19853
19854          ˇ
19855          fn main() {
19856              println!("hello there");
19857
19858              println!("around the");
19859              println!("world");
19860          }
19861        "#
19862        .unindent(),
19863    );
19864}
19865
19866#[gpui::test]
19867async fn test_diff_base_change_with_expanded_diff_hunks(
19868    executor: BackgroundExecutor,
19869    cx: &mut TestAppContext,
19870) {
19871    init_test(cx, |_| {});
19872
19873    let mut cx = EditorTestContext::new(cx).await;
19874
19875    let diff_base = r#"
19876        use some::mod1;
19877        use some::mod2;
19878
19879        const A: u32 = 42;
19880        const B: u32 = 42;
19881        const C: u32 = 42;
19882
19883        fn main() {
19884            println!("hello");
19885
19886            println!("world");
19887        }
19888        "#
19889    .unindent();
19890
19891    cx.set_state(
19892        &r#"
19893        use some::mod2;
19894
19895        const A: u32 = 42;
19896        const C: u32 = 42;
19897
19898        fn main(ˇ) {
19899            //println!("hello");
19900
19901            println!("world");
19902            //
19903            //
19904        }
19905        "#
19906        .unindent(),
19907    );
19908
19909    cx.set_head_text(&diff_base);
19910    executor.run_until_parked();
19911
19912    cx.update_editor(|editor, window, cx| {
19913        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
19914    });
19915    executor.run_until_parked();
19916    cx.assert_state_with_diff(
19917        r#"
19918        - use some::mod1;
19919          use some::mod2;
19920
19921          const A: u32 = 42;
19922        - const B: u32 = 42;
19923          const C: u32 = 42;
19924
19925          fn main(ˇ) {
19926        -     println!("hello");
19927        +     //println!("hello");
19928
19929              println!("world");
19930        +     //
19931        +     //
19932          }
19933        "#
19934        .unindent(),
19935    );
19936
19937    cx.set_head_text("new diff base!");
19938    executor.run_until_parked();
19939    cx.assert_state_with_diff(
19940        r#"
19941        - new diff base!
19942        + use some::mod2;
19943        +
19944        + const A: u32 = 42;
19945        + const C: u32 = 42;
19946        +
19947        + fn main(ˇ) {
19948        +     //println!("hello");
19949        +
19950        +     println!("world");
19951        +     //
19952        +     //
19953        + }
19954        "#
19955        .unindent(),
19956    );
19957}
19958
19959#[gpui::test]
19960async fn test_toggle_diff_expand_in_multi_buffer(cx: &mut TestAppContext) {
19961    init_test(cx, |_| {});
19962
19963    let file_1_old = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19964    let file_1_new = "aaa\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj";
19965    let file_2_old = "lll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19966    let file_2_new = "lll\nmmm\nNNN\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu";
19967    let file_3_old = "111\n222\n333\n444\n555\n777\n888\n999\n000\n!!!";
19968    let file_3_new = "111\n222\n333\n444\n555\n666\n777\n888\n999\n000\n!!!";
19969
19970    let buffer_1 = cx.new(|cx| Buffer::local(file_1_new.to_string(), cx));
19971    let buffer_2 = cx.new(|cx| Buffer::local(file_2_new.to_string(), cx));
19972    let buffer_3 = cx.new(|cx| Buffer::local(file_3_new.to_string(), cx));
19973
19974    let multi_buffer = cx.new(|cx| {
19975        let mut multibuffer = MultiBuffer::new(ReadWrite);
19976        multibuffer.push_excerpts(
19977            buffer_1.clone(),
19978            [
19979                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19980                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19981                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19982            ],
19983            cx,
19984        );
19985        multibuffer.push_excerpts(
19986            buffer_2.clone(),
19987            [
19988                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19989                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19990                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
19991            ],
19992            cx,
19993        );
19994        multibuffer.push_excerpts(
19995            buffer_3.clone(),
19996            [
19997                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
19998                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
19999                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 3)),
20000            ],
20001            cx,
20002        );
20003        multibuffer
20004    });
20005
20006    let editor =
20007        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20008    editor
20009        .update(cx, |editor, _window, cx| {
20010            for (buffer, diff_base) in [
20011                (buffer_1.clone(), file_1_old),
20012                (buffer_2.clone(), file_2_old),
20013                (buffer_3.clone(), file_3_old),
20014            ] {
20015                let diff = cx.new(|cx| BufferDiff::new_with_base_text(diff_base, &buffer, cx));
20016                editor
20017                    .buffer
20018                    .update(cx, |buffer, cx| buffer.add_diff(diff, cx));
20019            }
20020        })
20021        .unwrap();
20022
20023    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20024    cx.run_until_parked();
20025
20026    cx.assert_editor_state(
20027        &"
20028            ˇaaa
20029            ccc
20030            ddd
20031
20032            ggg
20033            hhh
20034
20035
20036            lll
20037            mmm
20038            NNN
20039
20040            qqq
20041            rrr
20042
20043            uuu
20044            111
20045            222
20046            333
20047
20048            666
20049            777
20050
20051            000
20052            !!!"
20053        .unindent(),
20054    );
20055
20056    cx.update_editor(|editor, window, cx| {
20057        editor.select_all(&SelectAll, window, cx);
20058        editor.toggle_selected_diff_hunks(&ToggleSelectedDiffHunks, window, cx);
20059    });
20060    cx.executor().run_until_parked();
20061
20062    cx.assert_state_with_diff(
20063        "
20064            «aaa
20065          - bbb
20066            ccc
20067            ddd
20068
20069            ggg
20070            hhh
20071
20072
20073            lll
20074            mmm
20075          - nnn
20076          + NNN
20077
20078            qqq
20079            rrr
20080
20081            uuu
20082            111
20083            222
20084            333
20085
20086          + 666
20087            777
20088
20089            000
20090            !!!ˇ»"
20091            .unindent(),
20092    );
20093}
20094
20095#[gpui::test]
20096async fn test_expand_diff_hunk_at_excerpt_boundary(cx: &mut TestAppContext) {
20097    init_test(cx, |_| {});
20098
20099    let base = "aaa\nbbb\nccc\nddd\neee\nfff\nggg\n";
20100    let text = "aaa\nBBB\nBB2\nccc\nDDD\nEEE\nfff\nggg\nhhh\niii\n";
20101
20102    let buffer = cx.new(|cx| Buffer::local(text.to_string(), cx));
20103    let multi_buffer = cx.new(|cx| {
20104        let mut multibuffer = MultiBuffer::new(ReadWrite);
20105        multibuffer.push_excerpts(
20106            buffer.clone(),
20107            [
20108                ExcerptRange::new(Point::new(0, 0)..Point::new(2, 0)),
20109                ExcerptRange::new(Point::new(4, 0)..Point::new(7, 0)),
20110                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 0)),
20111            ],
20112            cx,
20113        );
20114        multibuffer
20115    });
20116
20117    let editor =
20118        cx.add_window(|window, cx| Editor::new(EditorMode::full(), multi_buffer, None, window, cx));
20119    editor
20120        .update(cx, |editor, _window, cx| {
20121            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base, &buffer, cx));
20122            editor
20123                .buffer
20124                .update(cx, |buffer, cx| buffer.add_diff(diff, cx))
20125        })
20126        .unwrap();
20127
20128    let mut cx = EditorTestContext::for_editor(editor, cx).await;
20129    cx.run_until_parked();
20130
20131    cx.update_editor(|editor, window, cx| {
20132        editor.expand_all_diff_hunks(&Default::default(), window, cx)
20133    });
20134    cx.executor().run_until_parked();
20135
20136    // When the start of a hunk coincides with the start of its excerpt,
20137    // the hunk is expanded. When the start of a hunk is earlier than
20138    // the start of its excerpt, the hunk is not expanded.
20139    cx.assert_state_with_diff(
20140        "
20141            ˇaaa
20142          - bbb
20143          + BBB
20144
20145          - ddd
20146          - eee
20147          + DDD
20148          + EEE
20149            fff
20150
20151            iii
20152        "
20153        .unindent(),
20154    );
20155}
20156
20157#[gpui::test]
20158async fn test_edits_around_expanded_insertion_hunks(
20159    executor: BackgroundExecutor,
20160    cx: &mut TestAppContext,
20161) {
20162    init_test(cx, |_| {});
20163
20164    let mut cx = EditorTestContext::new(cx).await;
20165
20166    let diff_base = r#"
20167        use some::mod1;
20168        use some::mod2;
20169
20170        const A: u32 = 42;
20171
20172        fn main() {
20173            println!("hello");
20174
20175            println!("world");
20176        }
20177        "#
20178    .unindent();
20179    executor.run_until_parked();
20180    cx.set_state(
20181        &r#"
20182        use some::mod1;
20183        use some::mod2;
20184
20185        const A: u32 = 42;
20186        const B: u32 = 42;
20187        const C: u32 = 42;
20188        ˇ
20189
20190        fn main() {
20191            println!("hello");
20192
20193            println!("world");
20194        }
20195        "#
20196        .unindent(),
20197    );
20198
20199    cx.set_head_text(&diff_base);
20200    executor.run_until_parked();
20201
20202    cx.update_editor(|editor, window, cx| {
20203        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20204    });
20205    executor.run_until_parked();
20206
20207    cx.assert_state_with_diff(
20208        r#"
20209        use some::mod1;
20210        use some::mod2;
20211
20212        const A: u32 = 42;
20213      + const B: u32 = 42;
20214      + const C: u32 = 42;
20215      + ˇ
20216
20217        fn main() {
20218            println!("hello");
20219
20220            println!("world");
20221        }
20222      "#
20223        .unindent(),
20224    );
20225
20226    cx.update_editor(|editor, window, cx| editor.handle_input("const D: u32 = 42;\n", window, cx));
20227    executor.run_until_parked();
20228
20229    cx.assert_state_with_diff(
20230        r#"
20231        use some::mod1;
20232        use some::mod2;
20233
20234        const A: u32 = 42;
20235      + const B: u32 = 42;
20236      + const C: u32 = 42;
20237      + const D: u32 = 42;
20238      + ˇ
20239
20240        fn main() {
20241            println!("hello");
20242
20243            println!("world");
20244        }
20245      "#
20246        .unindent(),
20247    );
20248
20249    cx.update_editor(|editor, window, cx| editor.handle_input("const E: u32 = 42;\n", window, cx));
20250    executor.run_until_parked();
20251
20252    cx.assert_state_with_diff(
20253        r#"
20254        use some::mod1;
20255        use some::mod2;
20256
20257        const A: u32 = 42;
20258      + const B: u32 = 42;
20259      + const C: u32 = 42;
20260      + const D: u32 = 42;
20261      + const E: u32 = 42;
20262      + ˇ
20263
20264        fn main() {
20265            println!("hello");
20266
20267            println!("world");
20268        }
20269      "#
20270        .unindent(),
20271    );
20272
20273    cx.update_editor(|editor, window, cx| {
20274        editor.delete_line(&DeleteLine, window, cx);
20275    });
20276    executor.run_until_parked();
20277
20278    cx.assert_state_with_diff(
20279        r#"
20280        use some::mod1;
20281        use some::mod2;
20282
20283        const A: u32 = 42;
20284      + const B: u32 = 42;
20285      + const C: u32 = 42;
20286      + const D: u32 = 42;
20287      + const E: u32 = 42;
20288        ˇ
20289        fn main() {
20290            println!("hello");
20291
20292            println!("world");
20293        }
20294      "#
20295        .unindent(),
20296    );
20297
20298    cx.update_editor(|editor, window, cx| {
20299        editor.move_up(&MoveUp, window, cx);
20300        editor.delete_line(&DeleteLine, window, cx);
20301        editor.move_up(&MoveUp, window, cx);
20302        editor.delete_line(&DeleteLine, window, cx);
20303        editor.move_up(&MoveUp, window, cx);
20304        editor.delete_line(&DeleteLine, window, cx);
20305    });
20306    executor.run_until_parked();
20307    cx.assert_state_with_diff(
20308        r#"
20309        use some::mod1;
20310        use some::mod2;
20311
20312        const A: u32 = 42;
20313      + const B: u32 = 42;
20314        ˇ
20315        fn main() {
20316            println!("hello");
20317
20318            println!("world");
20319        }
20320      "#
20321        .unindent(),
20322    );
20323
20324    cx.update_editor(|editor, window, cx| {
20325        editor.select_up_by_lines(&SelectUpByLines { lines: 5 }, window, cx);
20326        editor.delete_line(&DeleteLine, window, cx);
20327    });
20328    executor.run_until_parked();
20329    cx.assert_state_with_diff(
20330        r#"
20331        ˇ
20332        fn main() {
20333            println!("hello");
20334
20335            println!("world");
20336        }
20337      "#
20338        .unindent(),
20339    );
20340}
20341
20342#[gpui::test]
20343async fn test_toggling_adjacent_diff_hunks(cx: &mut TestAppContext) {
20344    init_test(cx, |_| {});
20345
20346    let mut cx = EditorTestContext::new(cx).await;
20347    cx.set_head_text(indoc! { "
20348        one
20349        two
20350        three
20351        four
20352        five
20353        "
20354    });
20355    cx.set_state(indoc! { "
20356        one
20357        ˇthree
20358        five
20359    "});
20360    cx.run_until_parked();
20361    cx.update_editor(|editor, window, cx| {
20362        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20363    });
20364    cx.assert_state_with_diff(
20365        indoc! { "
20366        one
20367      - two
20368        ˇthree
20369      - four
20370        five
20371    "}
20372        .to_string(),
20373    );
20374    cx.update_editor(|editor, window, cx| {
20375        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20376    });
20377
20378    cx.assert_state_with_diff(
20379        indoc! { "
20380        one
20381        ˇthree
20382        five
20383    "}
20384        .to_string(),
20385    );
20386
20387    cx.set_state(indoc! { "
20388        one
20389        ˇTWO
20390        three
20391        four
20392        five
20393    "});
20394    cx.run_until_parked();
20395    cx.update_editor(|editor, window, cx| {
20396        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20397    });
20398
20399    cx.assert_state_with_diff(
20400        indoc! { "
20401            one
20402          - two
20403          + ˇTWO
20404            three
20405            four
20406            five
20407        "}
20408        .to_string(),
20409    );
20410    cx.update_editor(|editor, window, cx| {
20411        editor.move_up(&Default::default(), window, cx);
20412        editor.toggle_selected_diff_hunks(&Default::default(), window, cx);
20413    });
20414    cx.assert_state_with_diff(
20415        indoc! { "
20416            one
20417            ˇTWO
20418            three
20419            four
20420            five
20421        "}
20422        .to_string(),
20423    );
20424}
20425
20426#[gpui::test]
20427async fn test_edits_around_expanded_deletion_hunks(
20428    executor: BackgroundExecutor,
20429    cx: &mut TestAppContext,
20430) {
20431    init_test(cx, |_| {});
20432
20433    let mut cx = EditorTestContext::new(cx).await;
20434
20435    let diff_base = r#"
20436        use some::mod1;
20437        use some::mod2;
20438
20439        const A: u32 = 42;
20440        const B: u32 = 42;
20441        const C: u32 = 42;
20442
20443
20444        fn main() {
20445            println!("hello");
20446
20447            println!("world");
20448        }
20449    "#
20450    .unindent();
20451    executor.run_until_parked();
20452    cx.set_state(
20453        &r#"
20454        use some::mod1;
20455        use some::mod2;
20456
20457        ˇconst B: u32 = 42;
20458        const C: u32 = 42;
20459
20460
20461        fn main() {
20462            println!("hello");
20463
20464            println!("world");
20465        }
20466        "#
20467        .unindent(),
20468    );
20469
20470    cx.set_head_text(&diff_base);
20471    executor.run_until_parked();
20472
20473    cx.update_editor(|editor, window, cx| {
20474        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20475    });
20476    executor.run_until_parked();
20477
20478    cx.assert_state_with_diff(
20479        r#"
20480        use some::mod1;
20481        use some::mod2;
20482
20483      - const A: u32 = 42;
20484        ˇconst B: u32 = 42;
20485        const C: u32 = 42;
20486
20487
20488        fn main() {
20489            println!("hello");
20490
20491            println!("world");
20492        }
20493      "#
20494        .unindent(),
20495    );
20496
20497    cx.update_editor(|editor, window, cx| {
20498        editor.delete_line(&DeleteLine, window, cx);
20499    });
20500    executor.run_until_parked();
20501    cx.assert_state_with_diff(
20502        r#"
20503        use some::mod1;
20504        use some::mod2;
20505
20506      - const A: u32 = 42;
20507      - const B: u32 = 42;
20508        ˇconst C: u32 = 42;
20509
20510
20511        fn main() {
20512            println!("hello");
20513
20514            println!("world");
20515        }
20516      "#
20517        .unindent(),
20518    );
20519
20520    cx.update_editor(|editor, window, cx| {
20521        editor.delete_line(&DeleteLine, window, cx);
20522    });
20523    executor.run_until_parked();
20524    cx.assert_state_with_diff(
20525        r#"
20526        use some::mod1;
20527        use some::mod2;
20528
20529      - const A: u32 = 42;
20530      - const B: u32 = 42;
20531      - const C: u32 = 42;
20532        ˇ
20533
20534        fn main() {
20535            println!("hello");
20536
20537            println!("world");
20538        }
20539      "#
20540        .unindent(),
20541    );
20542
20543    cx.update_editor(|editor, window, cx| {
20544        editor.handle_input("replacement", window, cx);
20545    });
20546    executor.run_until_parked();
20547    cx.assert_state_with_diff(
20548        r#"
20549        use some::mod1;
20550        use some::mod2;
20551
20552      - const A: u32 = 42;
20553      - const B: u32 = 42;
20554      - const C: u32 = 42;
20555      -
20556      + replacementˇ
20557
20558        fn main() {
20559            println!("hello");
20560
20561            println!("world");
20562        }
20563      "#
20564        .unindent(),
20565    );
20566}
20567
20568#[gpui::test]
20569async fn test_backspace_after_deletion_hunk(executor: BackgroundExecutor, cx: &mut TestAppContext) {
20570    init_test(cx, |_| {});
20571
20572    let mut cx = EditorTestContext::new(cx).await;
20573
20574    let base_text = r#"
20575        one
20576        two
20577        three
20578        four
20579        five
20580    "#
20581    .unindent();
20582    executor.run_until_parked();
20583    cx.set_state(
20584        &r#"
20585        one
20586        two
20587        fˇour
20588        five
20589        "#
20590        .unindent(),
20591    );
20592
20593    cx.set_head_text(&base_text);
20594    executor.run_until_parked();
20595
20596    cx.update_editor(|editor, window, cx| {
20597        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20598    });
20599    executor.run_until_parked();
20600
20601    cx.assert_state_with_diff(
20602        r#"
20603          one
20604          two
20605        - three
20606          fˇour
20607          five
20608        "#
20609        .unindent(),
20610    );
20611
20612    cx.update_editor(|editor, window, cx| {
20613        editor.backspace(&Backspace, window, cx);
20614        editor.backspace(&Backspace, window, cx);
20615    });
20616    executor.run_until_parked();
20617    cx.assert_state_with_diff(
20618        r#"
20619          one
20620          two
20621        - threeˇ
20622        - four
20623        + our
20624          five
20625        "#
20626        .unindent(),
20627    );
20628}
20629
20630#[gpui::test]
20631async fn test_edit_after_expanded_modification_hunk(
20632    executor: BackgroundExecutor,
20633    cx: &mut TestAppContext,
20634) {
20635    init_test(cx, |_| {});
20636
20637    let mut cx = EditorTestContext::new(cx).await;
20638
20639    let diff_base = r#"
20640        use some::mod1;
20641        use some::mod2;
20642
20643        const A: u32 = 42;
20644        const B: u32 = 42;
20645        const C: u32 = 42;
20646        const D: u32 = 42;
20647
20648
20649        fn main() {
20650            println!("hello");
20651
20652            println!("world");
20653        }"#
20654    .unindent();
20655
20656    cx.set_state(
20657        &r#"
20658        use some::mod1;
20659        use some::mod2;
20660
20661        const A: u32 = 42;
20662        const B: u32 = 42;
20663        const C: u32 = 43ˇ
20664        const D: u32 = 42;
20665
20666
20667        fn main() {
20668            println!("hello");
20669
20670            println!("world");
20671        }"#
20672        .unindent(),
20673    );
20674
20675    cx.set_head_text(&diff_base);
20676    executor.run_until_parked();
20677    cx.update_editor(|editor, window, cx| {
20678        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
20679    });
20680    executor.run_until_parked();
20681
20682    cx.assert_state_with_diff(
20683        r#"
20684        use some::mod1;
20685        use some::mod2;
20686
20687        const A: u32 = 42;
20688        const B: u32 = 42;
20689      - const C: u32 = 42;
20690      + const C: u32 = 43ˇ
20691        const D: u32 = 42;
20692
20693
20694        fn main() {
20695            println!("hello");
20696
20697            println!("world");
20698        }"#
20699        .unindent(),
20700    );
20701
20702    cx.update_editor(|editor, window, cx| {
20703        editor.handle_input("\nnew_line\n", window, cx);
20704    });
20705    executor.run_until_parked();
20706
20707    cx.assert_state_with_diff(
20708        r#"
20709        use some::mod1;
20710        use some::mod2;
20711
20712        const A: u32 = 42;
20713        const B: u32 = 42;
20714      - const C: u32 = 42;
20715      + const C: u32 = 43
20716      + new_line
20717      + ˇ
20718        const D: u32 = 42;
20719
20720
20721        fn main() {
20722            println!("hello");
20723
20724            println!("world");
20725        }"#
20726        .unindent(),
20727    );
20728}
20729
20730#[gpui::test]
20731async fn test_stage_and_unstage_added_file_hunk(
20732    executor: BackgroundExecutor,
20733    cx: &mut TestAppContext,
20734) {
20735    init_test(cx, |_| {});
20736
20737    let mut cx = EditorTestContext::new(cx).await;
20738    cx.update_editor(|editor, _, cx| {
20739        editor.set_expand_all_diff_hunks(cx);
20740    });
20741
20742    let working_copy = r#"
20743            ˇfn main() {
20744                println!("hello, world!");
20745            }
20746        "#
20747    .unindent();
20748
20749    cx.set_state(&working_copy);
20750    executor.run_until_parked();
20751
20752    cx.assert_state_with_diff(
20753        r#"
20754            + ˇfn main() {
20755            +     println!("hello, world!");
20756            + }
20757        "#
20758        .unindent(),
20759    );
20760    cx.assert_index_text(None);
20761
20762    cx.update_editor(|editor, window, cx| {
20763        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20764    });
20765    executor.run_until_parked();
20766    cx.assert_index_text(Some(&working_copy.replace("ˇ", "")));
20767    cx.assert_state_with_diff(
20768        r#"
20769            + ˇfn main() {
20770            +     println!("hello, world!");
20771            + }
20772        "#
20773        .unindent(),
20774    );
20775
20776    cx.update_editor(|editor, window, cx| {
20777        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
20778    });
20779    executor.run_until_parked();
20780    cx.assert_index_text(None);
20781}
20782
20783async fn setup_indent_guides_editor(
20784    text: &str,
20785    cx: &mut TestAppContext,
20786) -> (BufferId, EditorTestContext) {
20787    init_test(cx, |_| {});
20788
20789    let mut cx = EditorTestContext::new(cx).await;
20790
20791    let buffer_id = cx.update_editor(|editor, window, cx| {
20792        editor.set_text(text, window, cx);
20793        let buffer_ids = editor.buffer().read(cx).excerpt_buffer_ids();
20794
20795        buffer_ids[0]
20796    });
20797
20798    (buffer_id, cx)
20799}
20800
20801fn assert_indent_guides(
20802    range: Range<u32>,
20803    expected: Vec<IndentGuide>,
20804    active_indices: Option<Vec<usize>>,
20805    cx: &mut EditorTestContext,
20806) {
20807    let indent_guides = cx.update_editor(|editor, window, cx| {
20808        let snapshot = editor.snapshot(window, cx).display_snapshot;
20809        let mut indent_guides: Vec<_> = crate::indent_guides::indent_guides_in_range(
20810            editor,
20811            MultiBufferRow(range.start)..MultiBufferRow(range.end),
20812            true,
20813            &snapshot,
20814            cx,
20815        );
20816
20817        indent_guides.sort_by(|a, b| {
20818            a.depth.cmp(&b.depth).then(
20819                a.start_row
20820                    .cmp(&b.start_row)
20821                    .then(a.end_row.cmp(&b.end_row)),
20822            )
20823        });
20824        indent_guides
20825    });
20826
20827    if let Some(expected) = active_indices {
20828        let active_indices = cx.update_editor(|editor, window, cx| {
20829            let snapshot = editor.snapshot(window, cx).display_snapshot;
20830            editor.find_active_indent_guide_indices(&indent_guides, &snapshot, window, cx)
20831        });
20832
20833        assert_eq!(
20834            active_indices.unwrap().into_iter().collect::<Vec<_>>(),
20835            expected,
20836            "Active indent guide indices do not match"
20837        );
20838    }
20839
20840    assert_eq!(indent_guides, expected, "Indent guides do not match");
20841}
20842
20843fn indent_guide(buffer_id: BufferId, start_row: u32, end_row: u32, depth: u32) -> IndentGuide {
20844    IndentGuide {
20845        buffer_id,
20846        start_row: MultiBufferRow(start_row),
20847        end_row: MultiBufferRow(end_row),
20848        depth,
20849        tab_size: 4,
20850        settings: IndentGuideSettings {
20851            enabled: true,
20852            line_width: 1,
20853            active_line_width: 1,
20854            coloring: IndentGuideColoring::default(),
20855            background_coloring: IndentGuideBackgroundColoring::default(),
20856        },
20857    }
20858}
20859
20860#[gpui::test]
20861async fn test_indent_guide_single_line(cx: &mut TestAppContext) {
20862    let (buffer_id, mut cx) = setup_indent_guides_editor(
20863        &"
20864        fn main() {
20865            let a = 1;
20866        }"
20867        .unindent(),
20868        cx,
20869    )
20870    .await;
20871
20872    assert_indent_guides(0..3, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
20873}
20874
20875#[gpui::test]
20876async fn test_indent_guide_simple_block(cx: &mut TestAppContext) {
20877    let (buffer_id, mut cx) = setup_indent_guides_editor(
20878        &"
20879        fn main() {
20880            let a = 1;
20881            let b = 2;
20882        }"
20883        .unindent(),
20884        cx,
20885    )
20886    .await;
20887
20888    assert_indent_guides(0..4, vec![indent_guide(buffer_id, 1, 2, 0)], None, &mut cx);
20889}
20890
20891#[gpui::test]
20892async fn test_indent_guide_nested(cx: &mut TestAppContext) {
20893    let (buffer_id, mut cx) = setup_indent_guides_editor(
20894        &"
20895        fn main() {
20896            let a = 1;
20897            if a == 3 {
20898                let b = 2;
20899            } else {
20900                let c = 3;
20901            }
20902        }"
20903        .unindent(),
20904        cx,
20905    )
20906    .await;
20907
20908    assert_indent_guides(
20909        0..8,
20910        vec![
20911            indent_guide(buffer_id, 1, 6, 0),
20912            indent_guide(buffer_id, 3, 3, 1),
20913            indent_guide(buffer_id, 5, 5, 1),
20914        ],
20915        None,
20916        &mut cx,
20917    );
20918}
20919
20920#[gpui::test]
20921async fn test_indent_guide_tab(cx: &mut TestAppContext) {
20922    let (buffer_id, mut cx) = setup_indent_guides_editor(
20923        &"
20924        fn main() {
20925            let a = 1;
20926                let b = 2;
20927            let c = 3;
20928        }"
20929        .unindent(),
20930        cx,
20931    )
20932    .await;
20933
20934    assert_indent_guides(
20935        0..5,
20936        vec![
20937            indent_guide(buffer_id, 1, 3, 0),
20938            indent_guide(buffer_id, 2, 2, 1),
20939        ],
20940        None,
20941        &mut cx,
20942    );
20943}
20944
20945#[gpui::test]
20946async fn test_indent_guide_continues_on_empty_line(cx: &mut TestAppContext) {
20947    let (buffer_id, mut cx) = setup_indent_guides_editor(
20948        &"
20949        fn main() {
20950            let a = 1;
20951
20952            let c = 3;
20953        }"
20954        .unindent(),
20955        cx,
20956    )
20957    .await;
20958
20959    assert_indent_guides(0..5, vec![indent_guide(buffer_id, 1, 3, 0)], None, &mut cx);
20960}
20961
20962#[gpui::test]
20963async fn test_indent_guide_complex(cx: &mut TestAppContext) {
20964    let (buffer_id, mut cx) = setup_indent_guides_editor(
20965        &"
20966        fn main() {
20967            let a = 1;
20968
20969            let c = 3;
20970
20971            if a == 3 {
20972                let b = 2;
20973            } else {
20974                let c = 3;
20975            }
20976        }"
20977        .unindent(),
20978        cx,
20979    )
20980    .await;
20981
20982    assert_indent_guides(
20983        0..11,
20984        vec![
20985            indent_guide(buffer_id, 1, 9, 0),
20986            indent_guide(buffer_id, 6, 6, 1),
20987            indent_guide(buffer_id, 8, 8, 1),
20988        ],
20989        None,
20990        &mut cx,
20991    );
20992}
20993
20994#[gpui::test]
20995async fn test_indent_guide_starts_off_screen(cx: &mut TestAppContext) {
20996    let (buffer_id, mut cx) = setup_indent_guides_editor(
20997        &"
20998        fn main() {
20999            let a = 1;
21000
21001            let c = 3;
21002
21003            if a == 3 {
21004                let b = 2;
21005            } else {
21006                let c = 3;
21007            }
21008        }"
21009        .unindent(),
21010        cx,
21011    )
21012    .await;
21013
21014    assert_indent_guides(
21015        1..11,
21016        vec![
21017            indent_guide(buffer_id, 1, 9, 0),
21018            indent_guide(buffer_id, 6, 6, 1),
21019            indent_guide(buffer_id, 8, 8, 1),
21020        ],
21021        None,
21022        &mut cx,
21023    );
21024}
21025
21026#[gpui::test]
21027async fn test_indent_guide_ends_off_screen(cx: &mut TestAppContext) {
21028    let (buffer_id, mut cx) = setup_indent_guides_editor(
21029        &"
21030        fn main() {
21031            let a = 1;
21032
21033            let c = 3;
21034
21035            if a == 3 {
21036                let b = 2;
21037            } else {
21038                let c = 3;
21039            }
21040        }"
21041        .unindent(),
21042        cx,
21043    )
21044    .await;
21045
21046    assert_indent_guides(
21047        1..10,
21048        vec![
21049            indent_guide(buffer_id, 1, 9, 0),
21050            indent_guide(buffer_id, 6, 6, 1),
21051            indent_guide(buffer_id, 8, 8, 1),
21052        ],
21053        None,
21054        &mut cx,
21055    );
21056}
21057
21058#[gpui::test]
21059async fn test_indent_guide_with_folds(cx: &mut TestAppContext) {
21060    let (buffer_id, mut cx) = setup_indent_guides_editor(
21061        &"
21062        fn main() {
21063            if a {
21064                b(
21065                    c,
21066                    d,
21067                )
21068            } else {
21069                e(
21070                    f
21071                )
21072            }
21073        }"
21074        .unindent(),
21075        cx,
21076    )
21077    .await;
21078
21079    assert_indent_guides(
21080        0..11,
21081        vec![
21082            indent_guide(buffer_id, 1, 10, 0),
21083            indent_guide(buffer_id, 2, 5, 1),
21084            indent_guide(buffer_id, 7, 9, 1),
21085            indent_guide(buffer_id, 3, 4, 2),
21086            indent_guide(buffer_id, 8, 8, 2),
21087        ],
21088        None,
21089        &mut cx,
21090    );
21091
21092    cx.update_editor(|editor, window, cx| {
21093        editor.fold_at(MultiBufferRow(2), window, cx);
21094        assert_eq!(
21095            editor.display_text(cx),
21096            "
21097            fn main() {
21098                if a {
21099                    b(⋯
21100                    )
21101                } else {
21102                    e(
21103                        f
21104                    )
21105                }
21106            }"
21107            .unindent()
21108        );
21109    });
21110
21111    assert_indent_guides(
21112        0..11,
21113        vec![
21114            indent_guide(buffer_id, 1, 10, 0),
21115            indent_guide(buffer_id, 2, 5, 1),
21116            indent_guide(buffer_id, 7, 9, 1),
21117            indent_guide(buffer_id, 8, 8, 2),
21118        ],
21119        None,
21120        &mut cx,
21121    );
21122}
21123
21124#[gpui::test]
21125async fn test_indent_guide_without_brackets(cx: &mut TestAppContext) {
21126    let (buffer_id, mut cx) = setup_indent_guides_editor(
21127        &"
21128        block1
21129            block2
21130                block3
21131                    block4
21132            block2
21133        block1
21134        block1"
21135            .unindent(),
21136        cx,
21137    )
21138    .await;
21139
21140    assert_indent_guides(
21141        1..10,
21142        vec![
21143            indent_guide(buffer_id, 1, 4, 0),
21144            indent_guide(buffer_id, 2, 3, 1),
21145            indent_guide(buffer_id, 3, 3, 2),
21146        ],
21147        None,
21148        &mut cx,
21149    );
21150}
21151
21152#[gpui::test]
21153async fn test_indent_guide_ends_before_empty_line(cx: &mut TestAppContext) {
21154    let (buffer_id, mut cx) = setup_indent_guides_editor(
21155        &"
21156        block1
21157            block2
21158                block3
21159
21160        block1
21161        block1"
21162            .unindent(),
21163        cx,
21164    )
21165    .await;
21166
21167    assert_indent_guides(
21168        0..6,
21169        vec![
21170            indent_guide(buffer_id, 1, 2, 0),
21171            indent_guide(buffer_id, 2, 2, 1),
21172        ],
21173        None,
21174        &mut cx,
21175    );
21176}
21177
21178#[gpui::test]
21179async fn test_indent_guide_ignored_only_whitespace_lines(cx: &mut TestAppContext) {
21180    let (buffer_id, mut cx) = setup_indent_guides_editor(
21181        &"
21182        function component() {
21183        \treturn (
21184        \t\t\t
21185        \t\t<div>
21186        \t\t\t<abc></abc>
21187        \t\t</div>
21188        \t)
21189        }"
21190        .unindent(),
21191        cx,
21192    )
21193    .await;
21194
21195    assert_indent_guides(
21196        0..8,
21197        vec![
21198            indent_guide(buffer_id, 1, 6, 0),
21199            indent_guide(buffer_id, 2, 5, 1),
21200            indent_guide(buffer_id, 4, 4, 2),
21201        ],
21202        None,
21203        &mut cx,
21204    );
21205}
21206
21207#[gpui::test]
21208async fn test_indent_guide_fallback_to_next_non_entirely_whitespace_line(cx: &mut TestAppContext) {
21209    let (buffer_id, mut cx) = setup_indent_guides_editor(
21210        &"
21211        function component() {
21212        \treturn (
21213        \t
21214        \t\t<div>
21215        \t\t\t<abc></abc>
21216        \t\t</div>
21217        \t)
21218        }"
21219        .unindent(),
21220        cx,
21221    )
21222    .await;
21223
21224    assert_indent_guides(
21225        0..8,
21226        vec![
21227            indent_guide(buffer_id, 1, 6, 0),
21228            indent_guide(buffer_id, 2, 5, 1),
21229            indent_guide(buffer_id, 4, 4, 2),
21230        ],
21231        None,
21232        &mut cx,
21233    );
21234}
21235
21236#[gpui::test]
21237async fn test_indent_guide_continuing_off_screen(cx: &mut TestAppContext) {
21238    let (buffer_id, mut cx) = setup_indent_guides_editor(
21239        &"
21240        block1
21241
21242
21243
21244            block2
21245        "
21246        .unindent(),
21247        cx,
21248    )
21249    .await;
21250
21251    assert_indent_guides(0..1, vec![indent_guide(buffer_id, 1, 1, 0)], None, &mut cx);
21252}
21253
21254#[gpui::test]
21255async fn test_indent_guide_tabs(cx: &mut TestAppContext) {
21256    let (buffer_id, mut cx) = setup_indent_guides_editor(
21257        &"
21258        def a:
21259        \tb = 3
21260        \tif True:
21261        \t\tc = 4
21262        \t\td = 5
21263        \tprint(b)
21264        "
21265        .unindent(),
21266        cx,
21267    )
21268    .await;
21269
21270    assert_indent_guides(
21271        0..6,
21272        vec![
21273            indent_guide(buffer_id, 1, 5, 0),
21274            indent_guide(buffer_id, 3, 4, 1),
21275        ],
21276        None,
21277        &mut cx,
21278    );
21279}
21280
21281#[gpui::test]
21282async fn test_active_indent_guide_single_line(cx: &mut TestAppContext) {
21283    let (buffer_id, mut cx) = setup_indent_guides_editor(
21284        &"
21285    fn main() {
21286        let a = 1;
21287    }"
21288        .unindent(),
21289        cx,
21290    )
21291    .await;
21292
21293    cx.update_editor(|editor, window, cx| {
21294        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21295            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21296        });
21297    });
21298
21299    assert_indent_guides(
21300        0..3,
21301        vec![indent_guide(buffer_id, 1, 1, 0)],
21302        Some(vec![0]),
21303        &mut cx,
21304    );
21305}
21306
21307#[gpui::test]
21308async fn test_active_indent_guide_respect_indented_range(cx: &mut TestAppContext) {
21309    let (buffer_id, mut cx) = setup_indent_guides_editor(
21310        &"
21311    fn main() {
21312        if 1 == 2 {
21313            let a = 1;
21314        }
21315    }"
21316        .unindent(),
21317        cx,
21318    )
21319    .await;
21320
21321    cx.update_editor(|editor, window, cx| {
21322        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21323            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21324        });
21325    });
21326
21327    assert_indent_guides(
21328        0..4,
21329        vec![
21330            indent_guide(buffer_id, 1, 3, 0),
21331            indent_guide(buffer_id, 2, 2, 1),
21332        ],
21333        Some(vec![1]),
21334        &mut cx,
21335    );
21336
21337    cx.update_editor(|editor, window, cx| {
21338        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21339            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21340        });
21341    });
21342
21343    assert_indent_guides(
21344        0..4,
21345        vec![
21346            indent_guide(buffer_id, 1, 3, 0),
21347            indent_guide(buffer_id, 2, 2, 1),
21348        ],
21349        Some(vec![1]),
21350        &mut cx,
21351    );
21352
21353    cx.update_editor(|editor, window, cx| {
21354        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21355            s.select_ranges([Point::new(3, 0)..Point::new(3, 0)])
21356        });
21357    });
21358
21359    assert_indent_guides(
21360        0..4,
21361        vec![
21362            indent_guide(buffer_id, 1, 3, 0),
21363            indent_guide(buffer_id, 2, 2, 1),
21364        ],
21365        Some(vec![0]),
21366        &mut cx,
21367    );
21368}
21369
21370#[gpui::test]
21371async fn test_active_indent_guide_empty_line(cx: &mut TestAppContext) {
21372    let (buffer_id, mut cx) = setup_indent_guides_editor(
21373        &"
21374    fn main() {
21375        let a = 1;
21376
21377        let b = 2;
21378    }"
21379        .unindent(),
21380        cx,
21381    )
21382    .await;
21383
21384    cx.update_editor(|editor, window, cx| {
21385        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21386            s.select_ranges([Point::new(2, 0)..Point::new(2, 0)])
21387        });
21388    });
21389
21390    assert_indent_guides(
21391        0..5,
21392        vec![indent_guide(buffer_id, 1, 3, 0)],
21393        Some(vec![0]),
21394        &mut cx,
21395    );
21396}
21397
21398#[gpui::test]
21399async fn test_active_indent_guide_non_matching_indent(cx: &mut TestAppContext) {
21400    let (buffer_id, mut cx) = setup_indent_guides_editor(
21401        &"
21402    def m:
21403        a = 1
21404        pass"
21405            .unindent(),
21406        cx,
21407    )
21408    .await;
21409
21410    cx.update_editor(|editor, window, cx| {
21411        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21412            s.select_ranges([Point::new(1, 0)..Point::new(1, 0)])
21413        });
21414    });
21415
21416    assert_indent_guides(
21417        0..3,
21418        vec![indent_guide(buffer_id, 1, 2, 0)],
21419        Some(vec![0]),
21420        &mut cx,
21421    );
21422}
21423
21424#[gpui::test]
21425async fn test_indent_guide_with_expanded_diff_hunks(cx: &mut TestAppContext) {
21426    init_test(cx, |_| {});
21427    let mut cx = EditorTestContext::new(cx).await;
21428    let text = indoc! {
21429        "
21430        impl A {
21431            fn b() {
21432                0;
21433                3;
21434                5;
21435                6;
21436                7;
21437            }
21438        }
21439        "
21440    };
21441    let base_text = indoc! {
21442        "
21443        impl A {
21444            fn b() {
21445                0;
21446                1;
21447                2;
21448                3;
21449                4;
21450            }
21451            fn c() {
21452                5;
21453                6;
21454                7;
21455            }
21456        }
21457        "
21458    };
21459
21460    cx.update_editor(|editor, window, cx| {
21461        editor.set_text(text, window, cx);
21462
21463        editor.buffer().update(cx, |multibuffer, cx| {
21464            let buffer = multibuffer.as_singleton().unwrap();
21465            let diff = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer, cx));
21466
21467            multibuffer.set_all_diff_hunks_expanded(cx);
21468            multibuffer.add_diff(diff, cx);
21469
21470            buffer.read(cx).remote_id()
21471        })
21472    });
21473    cx.run_until_parked();
21474
21475    cx.assert_state_with_diff(
21476        indoc! { "
21477          impl A {
21478              fn b() {
21479                  0;
21480        -         1;
21481        -         2;
21482                  3;
21483        -         4;
21484        -     }
21485        -     fn c() {
21486                  5;
21487                  6;
21488                  7;
21489              }
21490          }
21491          ˇ"
21492        }
21493        .to_string(),
21494    );
21495
21496    let mut actual_guides = cx.update_editor(|editor, window, cx| {
21497        editor
21498            .snapshot(window, cx)
21499            .buffer_snapshot()
21500            .indent_guides_in_range(Anchor::min()..Anchor::max(), false, cx)
21501            .map(|guide| (guide.start_row..=guide.end_row, guide.depth))
21502            .collect::<Vec<_>>()
21503    });
21504    actual_guides.sort_by_key(|item| (*item.0.start(), item.1));
21505    assert_eq!(
21506        actual_guides,
21507        vec![
21508            (MultiBufferRow(1)..=MultiBufferRow(12), 0),
21509            (MultiBufferRow(2)..=MultiBufferRow(6), 1),
21510            (MultiBufferRow(9)..=MultiBufferRow(11), 1),
21511        ]
21512    );
21513}
21514
21515#[gpui::test]
21516async fn test_adjacent_diff_hunks(executor: BackgroundExecutor, cx: &mut TestAppContext) {
21517    init_test(cx, |_| {});
21518    let mut cx = EditorTestContext::new(cx).await;
21519
21520    let diff_base = r#"
21521        a
21522        b
21523        c
21524        "#
21525    .unindent();
21526
21527    cx.set_state(
21528        &r#"
21529        ˇA
21530        b
21531        C
21532        "#
21533        .unindent(),
21534    );
21535    cx.set_head_text(&diff_base);
21536    cx.update_editor(|editor, window, cx| {
21537        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21538    });
21539    executor.run_until_parked();
21540
21541    let both_hunks_expanded = r#"
21542        - a
21543        + ˇA
21544          b
21545        - c
21546        + C
21547        "#
21548    .unindent();
21549
21550    cx.assert_state_with_diff(both_hunks_expanded.clone());
21551
21552    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21553        let snapshot = editor.snapshot(window, cx);
21554        let hunks = editor
21555            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21556            .collect::<Vec<_>>();
21557        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21558        hunks
21559            .into_iter()
21560            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21561            .collect::<Vec<_>>()
21562    });
21563    assert_eq!(hunk_ranges.len(), 2);
21564
21565    cx.update_editor(|editor, _, cx| {
21566        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21567    });
21568    executor.run_until_parked();
21569
21570    let second_hunk_expanded = r#"
21571          ˇA
21572          b
21573        - c
21574        + C
21575        "#
21576    .unindent();
21577
21578    cx.assert_state_with_diff(second_hunk_expanded);
21579
21580    cx.update_editor(|editor, _, cx| {
21581        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21582    });
21583    executor.run_until_parked();
21584
21585    cx.assert_state_with_diff(both_hunks_expanded.clone());
21586
21587    cx.update_editor(|editor, _, cx| {
21588        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21589    });
21590    executor.run_until_parked();
21591
21592    let first_hunk_expanded = r#"
21593        - a
21594        + ˇA
21595          b
21596          C
21597        "#
21598    .unindent();
21599
21600    cx.assert_state_with_diff(first_hunk_expanded);
21601
21602    cx.update_editor(|editor, _, cx| {
21603        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21604    });
21605    executor.run_until_parked();
21606
21607    cx.assert_state_with_diff(both_hunks_expanded);
21608
21609    cx.set_state(
21610        &r#"
21611        ˇA
21612        b
21613        "#
21614        .unindent(),
21615    );
21616    cx.run_until_parked();
21617
21618    // TODO this cursor position seems bad
21619    cx.assert_state_with_diff(
21620        r#"
21621        - ˇa
21622        + A
21623          b
21624        "#
21625        .unindent(),
21626    );
21627
21628    cx.update_editor(|editor, window, cx| {
21629        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21630    });
21631
21632    cx.assert_state_with_diff(
21633        r#"
21634            - ˇa
21635            + A
21636              b
21637            - c
21638            "#
21639        .unindent(),
21640    );
21641
21642    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21643        let snapshot = editor.snapshot(window, cx);
21644        let hunks = editor
21645            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21646            .collect::<Vec<_>>();
21647        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21648        hunks
21649            .into_iter()
21650            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21651            .collect::<Vec<_>>()
21652    });
21653    assert_eq!(hunk_ranges.len(), 2);
21654
21655    cx.update_editor(|editor, _, cx| {
21656        editor.toggle_single_diff_hunk(hunk_ranges[1].clone(), cx);
21657    });
21658    executor.run_until_parked();
21659
21660    cx.assert_state_with_diff(
21661        r#"
21662        - ˇa
21663        + A
21664          b
21665        "#
21666        .unindent(),
21667    );
21668}
21669
21670#[gpui::test]
21671async fn test_toggle_deletion_hunk_at_start_of_file(
21672    executor: BackgroundExecutor,
21673    cx: &mut TestAppContext,
21674) {
21675    init_test(cx, |_| {});
21676    let mut cx = EditorTestContext::new(cx).await;
21677
21678    let diff_base = r#"
21679        a
21680        b
21681        c
21682        "#
21683    .unindent();
21684
21685    cx.set_state(
21686        &r#"
21687        ˇb
21688        c
21689        "#
21690        .unindent(),
21691    );
21692    cx.set_head_text(&diff_base);
21693    cx.update_editor(|editor, window, cx| {
21694        editor.expand_all_diff_hunks(&ExpandAllDiffHunks, window, cx);
21695    });
21696    executor.run_until_parked();
21697
21698    let hunk_expanded = r#"
21699        - a
21700          ˇb
21701          c
21702        "#
21703    .unindent();
21704
21705    cx.assert_state_with_diff(hunk_expanded.clone());
21706
21707    let hunk_ranges = cx.update_editor(|editor, window, cx| {
21708        let snapshot = editor.snapshot(window, cx);
21709        let hunks = editor
21710            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21711            .collect::<Vec<_>>();
21712        let excerpt_id = editor.buffer.read(cx).excerpt_ids()[0];
21713        hunks
21714            .into_iter()
21715            .map(|hunk| Anchor::range_in_buffer(excerpt_id, hunk.buffer_range))
21716            .collect::<Vec<_>>()
21717    });
21718    assert_eq!(hunk_ranges.len(), 1);
21719
21720    cx.update_editor(|editor, _, cx| {
21721        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21722    });
21723    executor.run_until_parked();
21724
21725    let hunk_collapsed = r#"
21726          ˇb
21727          c
21728        "#
21729    .unindent();
21730
21731    cx.assert_state_with_diff(hunk_collapsed);
21732
21733    cx.update_editor(|editor, _, cx| {
21734        editor.toggle_single_diff_hunk(hunk_ranges[0].clone(), cx);
21735    });
21736    executor.run_until_parked();
21737
21738    cx.assert_state_with_diff(hunk_expanded);
21739}
21740
21741#[gpui::test]
21742async fn test_display_diff_hunks(cx: &mut TestAppContext) {
21743    init_test(cx, |_| {});
21744
21745    let fs = FakeFs::new(cx.executor());
21746    fs.insert_tree(
21747        path!("/test"),
21748        json!({
21749            ".git": {},
21750            "file-1": "ONE\n",
21751            "file-2": "TWO\n",
21752            "file-3": "THREE\n",
21753        }),
21754    )
21755    .await;
21756
21757    fs.set_head_for_repo(
21758        path!("/test/.git").as_ref(),
21759        &[
21760            ("file-1", "one\n".into()),
21761            ("file-2", "two\n".into()),
21762            ("file-3", "three\n".into()),
21763        ],
21764        "deadbeef",
21765    );
21766
21767    let project = Project::test(fs, [path!("/test").as_ref()], cx).await;
21768    let mut buffers = vec![];
21769    for i in 1..=3 {
21770        let buffer = project
21771            .update(cx, |project, cx| {
21772                let path = format!(path!("/test/file-{}"), i);
21773                project.open_local_buffer(path, cx)
21774            })
21775            .await
21776            .unwrap();
21777        buffers.push(buffer);
21778    }
21779
21780    let multibuffer = cx.new(|cx| {
21781        let mut multibuffer = MultiBuffer::new(Capability::ReadWrite);
21782        multibuffer.set_all_diff_hunks_expanded(cx);
21783        for buffer in &buffers {
21784            let snapshot = buffer.read(cx).snapshot();
21785            multibuffer.set_excerpts_for_path(
21786                PathKey::with_sort_prefix(0, buffer.read(cx).file().unwrap().path().clone()),
21787                buffer.clone(),
21788                vec![text::Anchor::MIN.to_point(&snapshot)..text::Anchor::MAX.to_point(&snapshot)],
21789                2,
21790                cx,
21791            );
21792        }
21793        multibuffer
21794    });
21795
21796    let editor = cx.add_window(|window, cx| {
21797        Editor::new(EditorMode::full(), multibuffer, Some(project), window, cx)
21798    });
21799    cx.run_until_parked();
21800
21801    let snapshot = editor
21802        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21803        .unwrap();
21804    let hunks = snapshot
21805        .display_diff_hunks_for_rows(DisplayRow(0)..DisplayRow(u32::MAX), &Default::default())
21806        .map(|hunk| match hunk {
21807            DisplayDiffHunk::Unfolded {
21808                display_row_range, ..
21809            } => display_row_range,
21810            DisplayDiffHunk::Folded { .. } => unreachable!(),
21811        })
21812        .collect::<Vec<_>>();
21813    assert_eq!(
21814        hunks,
21815        [
21816            DisplayRow(2)..DisplayRow(4),
21817            DisplayRow(7)..DisplayRow(9),
21818            DisplayRow(12)..DisplayRow(14),
21819        ]
21820    );
21821}
21822
21823#[gpui::test]
21824async fn test_partially_staged_hunk(cx: &mut TestAppContext) {
21825    init_test(cx, |_| {});
21826
21827    let mut cx = EditorTestContext::new(cx).await;
21828    cx.set_head_text(indoc! { "
21829        one
21830        two
21831        three
21832        four
21833        five
21834        "
21835    });
21836    cx.set_index_text(indoc! { "
21837        one
21838        two
21839        three
21840        four
21841        five
21842        "
21843    });
21844    cx.set_state(indoc! {"
21845        one
21846        TWO
21847        ˇTHREE
21848        FOUR
21849        five
21850    "});
21851    cx.run_until_parked();
21852    cx.update_editor(|editor, window, cx| {
21853        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21854    });
21855    cx.run_until_parked();
21856    cx.assert_index_text(Some(indoc! {"
21857        one
21858        TWO
21859        THREE
21860        FOUR
21861        five
21862    "}));
21863    cx.set_state(indoc! { "
21864        one
21865        TWO
21866        ˇTHREE-HUNDRED
21867        FOUR
21868        five
21869    "});
21870    cx.run_until_parked();
21871    cx.update_editor(|editor, window, cx| {
21872        let snapshot = editor.snapshot(window, cx);
21873        let hunks = editor
21874            .diff_hunks_in_ranges(&[Anchor::min()..Anchor::max()], &snapshot.buffer_snapshot())
21875            .collect::<Vec<_>>();
21876        assert_eq!(hunks.len(), 1);
21877        assert_eq!(
21878            hunks[0].status(),
21879            DiffHunkStatus {
21880                kind: DiffHunkStatusKind::Modified,
21881                secondary: DiffHunkSecondaryStatus::OverlapsWithSecondaryHunk
21882            }
21883        );
21884
21885        editor.toggle_staged_selected_diff_hunks(&Default::default(), window, cx);
21886    });
21887    cx.run_until_parked();
21888    cx.assert_index_text(Some(indoc! {"
21889        one
21890        TWO
21891        THREE-HUNDRED
21892        FOUR
21893        five
21894    "}));
21895}
21896
21897#[gpui::test]
21898fn test_crease_insertion_and_rendering(cx: &mut TestAppContext) {
21899    init_test(cx, |_| {});
21900
21901    let editor = cx.add_window(|window, cx| {
21902        let buffer = MultiBuffer::build_simple("aaaaaa\nbbbbbb\ncccccc\nddddddd\n", cx);
21903        build_editor(buffer, window, cx)
21904    });
21905
21906    let render_args = Arc::new(Mutex::new(None));
21907    let snapshot = editor
21908        .update(cx, |editor, window, cx| {
21909            let snapshot = editor.buffer().read(cx).snapshot(cx);
21910            let range =
21911                snapshot.anchor_before(Point::new(1, 0))..snapshot.anchor_after(Point::new(2, 6));
21912
21913            struct RenderArgs {
21914                row: MultiBufferRow,
21915                folded: bool,
21916                callback: Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
21917            }
21918
21919            let crease = Crease::inline(
21920                range,
21921                FoldPlaceholder::test(),
21922                {
21923                    let toggle_callback = render_args.clone();
21924                    move |row, folded, callback, _window, _cx| {
21925                        *toggle_callback.lock() = Some(RenderArgs {
21926                            row,
21927                            folded,
21928                            callback,
21929                        });
21930                        div()
21931                    }
21932                },
21933                |_row, _folded, _window, _cx| div(),
21934            );
21935
21936            editor.insert_creases(Some(crease), cx);
21937            let snapshot = editor.snapshot(window, cx);
21938            let _div =
21939                snapshot.render_crease_toggle(MultiBufferRow(1), false, cx.entity(), window, cx);
21940            snapshot
21941        })
21942        .unwrap();
21943
21944    let render_args = render_args.lock().take().unwrap();
21945    assert_eq!(render_args.row, MultiBufferRow(1));
21946    assert!(!render_args.folded);
21947    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21948
21949    cx.update_window(*editor, |_, window, cx| {
21950        (render_args.callback)(true, window, cx)
21951    })
21952    .unwrap();
21953    let snapshot = editor
21954        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21955        .unwrap();
21956    assert!(snapshot.is_line_folded(MultiBufferRow(1)));
21957
21958    cx.update_window(*editor, |_, window, cx| {
21959        (render_args.callback)(false, window, cx)
21960    })
21961    .unwrap();
21962    let snapshot = editor
21963        .update(cx, |editor, window, cx| editor.snapshot(window, cx))
21964        .unwrap();
21965    assert!(!snapshot.is_line_folded(MultiBufferRow(1)));
21966}
21967
21968#[gpui::test]
21969async fn test_input_text(cx: &mut TestAppContext) {
21970    init_test(cx, |_| {});
21971    let mut cx = EditorTestContext::new(cx).await;
21972
21973    cx.set_state(
21974        &r#"ˇone
21975        two
21976
21977        three
21978        fourˇ
21979        five
21980
21981        siˇx"#
21982            .unindent(),
21983    );
21984
21985    cx.dispatch_action(HandleInput(String::new()));
21986    cx.assert_editor_state(
21987        &r#"ˇone
21988        two
21989
21990        three
21991        fourˇ
21992        five
21993
21994        siˇx"#
21995            .unindent(),
21996    );
21997
21998    cx.dispatch_action(HandleInput("AAAA".to_string()));
21999    cx.assert_editor_state(
22000        &r#"AAAAˇone
22001        two
22002
22003        three
22004        fourAAAAˇ
22005        five
22006
22007        siAAAAˇx"#
22008            .unindent(),
22009    );
22010}
22011
22012#[gpui::test]
22013async fn test_scroll_cursor_center_top_bottom(cx: &mut TestAppContext) {
22014    init_test(cx, |_| {});
22015
22016    let mut cx = EditorTestContext::new(cx).await;
22017    cx.set_state(
22018        r#"let foo = 1;
22019let foo = 2;
22020let foo = 3;
22021let fooˇ = 4;
22022let foo = 5;
22023let foo = 6;
22024let foo = 7;
22025let foo = 8;
22026let foo = 9;
22027let foo = 10;
22028let foo = 11;
22029let foo = 12;
22030let foo = 13;
22031let foo = 14;
22032let foo = 15;"#,
22033    );
22034
22035    cx.update_editor(|e, window, cx| {
22036        assert_eq!(
22037            e.next_scroll_position,
22038            NextScrollCursorCenterTopBottom::Center,
22039            "Default next scroll direction is center",
22040        );
22041
22042        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22043        assert_eq!(
22044            e.next_scroll_position,
22045            NextScrollCursorCenterTopBottom::Top,
22046            "After center, next scroll direction should be top",
22047        );
22048
22049        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22050        assert_eq!(
22051            e.next_scroll_position,
22052            NextScrollCursorCenterTopBottom::Bottom,
22053            "After top, next scroll direction should be bottom",
22054        );
22055
22056        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22057        assert_eq!(
22058            e.next_scroll_position,
22059            NextScrollCursorCenterTopBottom::Center,
22060            "After bottom, scrolling should start over",
22061        );
22062
22063        e.scroll_cursor_center_top_bottom(&ScrollCursorCenterTopBottom, window, cx);
22064        assert_eq!(
22065            e.next_scroll_position,
22066            NextScrollCursorCenterTopBottom::Top,
22067            "Scrolling continues if retriggered fast enough"
22068        );
22069    });
22070
22071    cx.executor()
22072        .advance_clock(SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT + Duration::from_millis(200));
22073    cx.executor().run_until_parked();
22074    cx.update_editor(|e, _, _| {
22075        assert_eq!(
22076            e.next_scroll_position,
22077            NextScrollCursorCenterTopBottom::Center,
22078            "If scrolling is not triggered fast enough, it should reset"
22079        );
22080    });
22081}
22082
22083#[gpui::test]
22084async fn test_goto_definition_with_find_all_references_fallback(cx: &mut TestAppContext) {
22085    init_test(cx, |_| {});
22086    let mut cx = EditorLspTestContext::new_rust(
22087        lsp::ServerCapabilities {
22088            definition_provider: Some(lsp::OneOf::Left(true)),
22089            references_provider: Some(lsp::OneOf::Left(true)),
22090            ..lsp::ServerCapabilities::default()
22091        },
22092        cx,
22093    )
22094    .await;
22095
22096    let set_up_lsp_handlers = |empty_go_to_definition: bool, cx: &mut EditorLspTestContext| {
22097        let go_to_definition = cx
22098            .lsp
22099            .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22100                move |params, _| async move {
22101                    if empty_go_to_definition {
22102                        Ok(None)
22103                    } else {
22104                        Ok(Some(lsp::GotoDefinitionResponse::Scalar(lsp::Location {
22105                            uri: params.text_document_position_params.text_document.uri,
22106                            range: lsp::Range::new(
22107                                lsp::Position::new(4, 3),
22108                                lsp::Position::new(4, 6),
22109                            ),
22110                        })))
22111                    }
22112                },
22113            );
22114        let references = cx
22115            .lsp
22116            .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22117                Ok(Some(vec![lsp::Location {
22118                    uri: params.text_document_position.text_document.uri,
22119                    range: lsp::Range::new(lsp::Position::new(0, 8), lsp::Position::new(0, 11)),
22120                }]))
22121            });
22122        (go_to_definition, references)
22123    };
22124
22125    cx.set_state(
22126        &r#"fn one() {
22127            let mut a = ˇtwo();
22128        }
22129
22130        fn two() {}"#
22131            .unindent(),
22132    );
22133    set_up_lsp_handlers(false, &mut cx);
22134    let navigated = cx
22135        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22136        .await
22137        .expect("Failed to navigate to definition");
22138    assert_eq!(
22139        navigated,
22140        Navigated::Yes,
22141        "Should have navigated to definition from the GetDefinition response"
22142    );
22143    cx.assert_editor_state(
22144        &r#"fn one() {
22145            let mut a = two();
22146        }
22147
22148        fn «twoˇ»() {}"#
22149            .unindent(),
22150    );
22151
22152    let editors = cx.update_workspace(|workspace, _, cx| {
22153        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22154    });
22155    cx.update_editor(|_, _, test_editor_cx| {
22156        assert_eq!(
22157            editors.len(),
22158            1,
22159            "Initially, only one, test, editor should be open in the workspace"
22160        );
22161        assert_eq!(
22162            test_editor_cx.entity(),
22163            editors.last().expect("Asserted len is 1").clone()
22164        );
22165    });
22166
22167    set_up_lsp_handlers(true, &mut cx);
22168    let navigated = cx
22169        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22170        .await
22171        .expect("Failed to navigate to lookup references");
22172    assert_eq!(
22173        navigated,
22174        Navigated::Yes,
22175        "Should have navigated to references as a fallback after empty GoToDefinition response"
22176    );
22177    // We should not change the selections in the existing file,
22178    // if opening another milti buffer with the references
22179    cx.assert_editor_state(
22180        &r#"fn one() {
22181            let mut a = two();
22182        }
22183
22184        fn «twoˇ»() {}"#
22185            .unindent(),
22186    );
22187    let editors = cx.update_workspace(|workspace, _, cx| {
22188        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22189    });
22190    cx.update_editor(|_, _, test_editor_cx| {
22191        assert_eq!(
22192            editors.len(),
22193            2,
22194            "After falling back to references search, we open a new editor with the results"
22195        );
22196        let references_fallback_text = editors
22197            .into_iter()
22198            .find(|new_editor| *new_editor != test_editor_cx.entity())
22199            .expect("Should have one non-test editor now")
22200            .read(test_editor_cx)
22201            .text(test_editor_cx);
22202        assert_eq!(
22203            references_fallback_text, "fn one() {\n    let mut a = two();\n}",
22204            "Should use the range from the references response and not the GoToDefinition one"
22205        );
22206    });
22207}
22208
22209#[gpui::test]
22210async fn test_goto_definition_no_fallback(cx: &mut TestAppContext) {
22211    init_test(cx, |_| {});
22212    cx.update(|cx| {
22213        let mut editor_settings = EditorSettings::get_global(cx).clone();
22214        editor_settings.go_to_definition_fallback = GoToDefinitionFallback::None;
22215        EditorSettings::override_global(editor_settings, cx);
22216    });
22217    let mut cx = EditorLspTestContext::new_rust(
22218        lsp::ServerCapabilities {
22219            definition_provider: Some(lsp::OneOf::Left(true)),
22220            references_provider: Some(lsp::OneOf::Left(true)),
22221            ..lsp::ServerCapabilities::default()
22222        },
22223        cx,
22224    )
22225    .await;
22226    let original_state = r#"fn one() {
22227        let mut a = ˇtwo();
22228    }
22229
22230    fn two() {}"#
22231        .unindent();
22232    cx.set_state(&original_state);
22233
22234    let mut go_to_definition = cx
22235        .lsp
22236        .set_request_handler::<lsp::request::GotoDefinition, _, _>(
22237            move |_, _| async move { Ok(None) },
22238        );
22239    let _references = cx
22240        .lsp
22241        .set_request_handler::<lsp::request::References, _, _>(move |_, _| async move {
22242            panic!("Should not call for references with no go to definition fallback")
22243        });
22244
22245    let navigated = cx
22246        .update_editor(|editor, window, cx| editor.go_to_definition(&GoToDefinition, window, cx))
22247        .await
22248        .expect("Failed to navigate to lookup references");
22249    go_to_definition
22250        .next()
22251        .await
22252        .expect("Should have called the go_to_definition handler");
22253
22254    assert_eq!(
22255        navigated,
22256        Navigated::No,
22257        "Should have navigated to references as a fallback after empty GoToDefinition response"
22258    );
22259    cx.assert_editor_state(&original_state);
22260    let editors = cx.update_workspace(|workspace, _, cx| {
22261        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22262    });
22263    cx.update_editor(|_, _, _| {
22264        assert_eq!(
22265            editors.len(),
22266            1,
22267            "After unsuccessful fallback, no other editor should have been opened"
22268        );
22269    });
22270}
22271
22272#[gpui::test]
22273async fn test_find_all_references_editor_reuse(cx: &mut TestAppContext) {
22274    init_test(cx, |_| {});
22275    let mut cx = EditorLspTestContext::new_rust(
22276        lsp::ServerCapabilities {
22277            references_provider: Some(lsp::OneOf::Left(true)),
22278            ..lsp::ServerCapabilities::default()
22279        },
22280        cx,
22281    )
22282    .await;
22283
22284    cx.set_state(
22285        &r#"
22286        fn one() {
22287            let mut a = two();
22288        }
22289
22290        fn ˇtwo() {}"#
22291            .unindent(),
22292    );
22293    cx.lsp
22294        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22295            Ok(Some(vec![
22296                lsp::Location {
22297                    uri: params.text_document_position.text_document.uri.clone(),
22298                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22299                },
22300                lsp::Location {
22301                    uri: params.text_document_position.text_document.uri,
22302                    range: lsp::Range::new(lsp::Position::new(4, 4), lsp::Position::new(4, 7)),
22303                },
22304            ]))
22305        });
22306    let navigated = cx
22307        .update_editor(|editor, window, cx| {
22308            editor.find_all_references(&FindAllReferences, window, cx)
22309        })
22310        .unwrap()
22311        .await
22312        .expect("Failed to navigate to references");
22313    assert_eq!(
22314        navigated,
22315        Navigated::Yes,
22316        "Should have navigated to references from the FindAllReferences response"
22317    );
22318    cx.assert_editor_state(
22319        &r#"fn one() {
22320            let mut a = two();
22321        }
22322
22323        fn ˇtwo() {}"#
22324            .unindent(),
22325    );
22326
22327    let editors = cx.update_workspace(|workspace, _, cx| {
22328        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22329    });
22330    cx.update_editor(|_, _, _| {
22331        assert_eq!(editors.len(), 2, "We should have opened a new multibuffer");
22332    });
22333
22334    cx.set_state(
22335        &r#"fn one() {
22336            let mut a = ˇtwo();
22337        }
22338
22339        fn two() {}"#
22340            .unindent(),
22341    );
22342    let navigated = cx
22343        .update_editor(|editor, window, cx| {
22344            editor.find_all_references(&FindAllReferences, window, cx)
22345        })
22346        .unwrap()
22347        .await
22348        .expect("Failed to navigate to references");
22349    assert_eq!(
22350        navigated,
22351        Navigated::Yes,
22352        "Should have navigated to references from the FindAllReferences response"
22353    );
22354    cx.assert_editor_state(
22355        &r#"fn one() {
22356            let mut a = ˇtwo();
22357        }
22358
22359        fn two() {}"#
22360            .unindent(),
22361    );
22362    let editors = cx.update_workspace(|workspace, _, cx| {
22363        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22364    });
22365    cx.update_editor(|_, _, _| {
22366        assert_eq!(
22367            editors.len(),
22368            2,
22369            "should have re-used the previous multibuffer"
22370        );
22371    });
22372
22373    cx.set_state(
22374        &r#"fn one() {
22375            let mut a = ˇtwo();
22376        }
22377        fn three() {}
22378        fn two() {}"#
22379            .unindent(),
22380    );
22381    cx.lsp
22382        .set_request_handler::<lsp::request::References, _, _>(move |params, _| async move {
22383            Ok(Some(vec![
22384                lsp::Location {
22385                    uri: params.text_document_position.text_document.uri.clone(),
22386                    range: lsp::Range::new(lsp::Position::new(0, 16), lsp::Position::new(0, 19)),
22387                },
22388                lsp::Location {
22389                    uri: params.text_document_position.text_document.uri,
22390                    range: lsp::Range::new(lsp::Position::new(5, 4), lsp::Position::new(5, 7)),
22391                },
22392            ]))
22393        });
22394    let navigated = cx
22395        .update_editor(|editor, window, cx| {
22396            editor.find_all_references(&FindAllReferences, window, cx)
22397        })
22398        .unwrap()
22399        .await
22400        .expect("Failed to navigate to references");
22401    assert_eq!(
22402        navigated,
22403        Navigated::Yes,
22404        "Should have navigated to references from the FindAllReferences response"
22405    );
22406    cx.assert_editor_state(
22407        &r#"fn one() {
22408                let mut a = ˇtwo();
22409            }
22410            fn three() {}
22411            fn two() {}"#
22412            .unindent(),
22413    );
22414    let editors = cx.update_workspace(|workspace, _, cx| {
22415        workspace.items_of_type::<Editor>(cx).collect::<Vec<_>>()
22416    });
22417    cx.update_editor(|_, _, _| {
22418        assert_eq!(
22419            editors.len(),
22420            3,
22421            "should have used a new multibuffer as offsets changed"
22422        );
22423    });
22424}
22425#[gpui::test]
22426async fn test_find_enclosing_node_with_task(cx: &mut TestAppContext) {
22427    init_test(cx, |_| {});
22428
22429    let language = Arc::new(Language::new(
22430        LanguageConfig::default(),
22431        Some(tree_sitter_rust::LANGUAGE.into()),
22432    ));
22433
22434    let text = r#"
22435        #[cfg(test)]
22436        mod tests() {
22437            #[test]
22438            fn runnable_1() {
22439                let a = 1;
22440            }
22441
22442            #[test]
22443            fn runnable_2() {
22444                let a = 1;
22445                let b = 2;
22446            }
22447        }
22448    "#
22449    .unindent();
22450
22451    let fs = FakeFs::new(cx.executor());
22452    fs.insert_file("/file.rs", Default::default()).await;
22453
22454    let project = Project::test(fs, ["/a".as_ref()], cx).await;
22455    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22456    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22457    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
22458    let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer.clone(), cx));
22459
22460    let editor = cx.new_window_entity(|window, cx| {
22461        Editor::new(
22462            EditorMode::full(),
22463            multi_buffer,
22464            Some(project.clone()),
22465            window,
22466            cx,
22467        )
22468    });
22469
22470    editor.update_in(cx, |editor, window, cx| {
22471        let snapshot = editor.buffer().read(cx).snapshot(cx);
22472        editor.tasks.insert(
22473            (buffer.read(cx).remote_id(), 3),
22474            RunnableTasks {
22475                templates: vec![],
22476                offset: snapshot.anchor_before(MultiBufferOffset(43)),
22477                column: 0,
22478                extra_variables: HashMap::default(),
22479                context_range: BufferOffset(43)..BufferOffset(85),
22480            },
22481        );
22482        editor.tasks.insert(
22483            (buffer.read(cx).remote_id(), 8),
22484            RunnableTasks {
22485                templates: vec![],
22486                offset: snapshot.anchor_before(MultiBufferOffset(86)),
22487                column: 0,
22488                extra_variables: HashMap::default(),
22489                context_range: BufferOffset(86)..BufferOffset(191),
22490            },
22491        );
22492
22493        // Test finding task when cursor is inside function body
22494        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22495            s.select_ranges([Point::new(4, 5)..Point::new(4, 5)])
22496        });
22497        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22498        assert_eq!(row, 3, "Should find task for cursor inside runnable_1");
22499
22500        // Test finding task when cursor is on function name
22501        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22502            s.select_ranges([Point::new(8, 4)..Point::new(8, 4)])
22503        });
22504        let (_, row, _) = editor.find_enclosing_node_task(cx).unwrap();
22505        assert_eq!(row, 8, "Should find task when cursor is on function name");
22506    });
22507}
22508
22509#[gpui::test]
22510async fn test_folding_buffers(cx: &mut TestAppContext) {
22511    init_test(cx, |_| {});
22512
22513    let sample_text_1 = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22514    let sample_text_2 = "llll\nmmmm\nnnnn\noooo\npppp\nqqqq\nrrrr\nssss\ntttt\nuuuu".to_string();
22515    let sample_text_3 = "vvvv\nwwww\nxxxx\nyyyy\nzzzz\n1111\n2222\n3333\n4444\n5555".to_string();
22516
22517    let fs = FakeFs::new(cx.executor());
22518    fs.insert_tree(
22519        path!("/a"),
22520        json!({
22521            "first.rs": sample_text_1,
22522            "second.rs": sample_text_2,
22523            "third.rs": sample_text_3,
22524        }),
22525    )
22526    .await;
22527    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22528    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22529    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22530    let worktree = project.update(cx, |project, cx| {
22531        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22532        assert_eq!(worktrees.len(), 1);
22533        worktrees.pop().unwrap()
22534    });
22535    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22536
22537    let buffer_1 = project
22538        .update(cx, |project, cx| {
22539            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22540        })
22541        .await
22542        .unwrap();
22543    let buffer_2 = project
22544        .update(cx, |project, cx| {
22545            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22546        })
22547        .await
22548        .unwrap();
22549    let buffer_3 = project
22550        .update(cx, |project, cx| {
22551            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22552        })
22553        .await
22554        .unwrap();
22555
22556    let multi_buffer = cx.new(|cx| {
22557        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22558        multi_buffer.push_excerpts(
22559            buffer_1.clone(),
22560            [
22561                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22562                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22563                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22564            ],
22565            cx,
22566        );
22567        multi_buffer.push_excerpts(
22568            buffer_2.clone(),
22569            [
22570                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22571                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22572                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22573            ],
22574            cx,
22575        );
22576        multi_buffer.push_excerpts(
22577            buffer_3.clone(),
22578            [
22579                ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0)),
22580                ExcerptRange::new(Point::new(5, 0)..Point::new(7, 0)),
22581                ExcerptRange::new(Point::new(9, 0)..Point::new(10, 4)),
22582            ],
22583            cx,
22584        );
22585        multi_buffer
22586    });
22587    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22588        Editor::new(
22589            EditorMode::full(),
22590            multi_buffer.clone(),
22591            Some(project.clone()),
22592            window,
22593            cx,
22594        )
22595    });
22596
22597    assert_eq!(
22598        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22599        "\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",
22600    );
22601
22602    multi_buffer_editor.update(cx, |editor, cx| {
22603        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22604    });
22605    assert_eq!(
22606        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22607        "\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",
22608        "After folding the first buffer, its text should not be displayed"
22609    );
22610
22611    multi_buffer_editor.update(cx, |editor, cx| {
22612        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22613    });
22614    assert_eq!(
22615        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22616        "\n\n\n\n\n\nvvvv\nwwww\nxxxx\n\n\n1111\n2222\n\n\n5555",
22617        "After folding the second buffer, its text should not be displayed"
22618    );
22619
22620    multi_buffer_editor.update(cx, |editor, cx| {
22621        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22622    });
22623    assert_eq!(
22624        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22625        "\n\n\n\n\n",
22626        "After folding the third buffer, its text should not be displayed"
22627    );
22628
22629    // Emulate selection inside the fold logic, that should work
22630    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22631        editor
22632            .snapshot(window, cx)
22633            .next_line_boundary(Point::new(0, 4));
22634    });
22635
22636    multi_buffer_editor.update(cx, |editor, cx| {
22637        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22638    });
22639    assert_eq!(
22640        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22641        "\n\n\n\nllll\nmmmm\nnnnn\n\n\nqqqq\nrrrr\n\n\nuuuu\n\n",
22642        "After unfolding the second buffer, its text should be displayed"
22643    );
22644
22645    // Typing inside of buffer 1 causes that buffer to be unfolded.
22646    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22647        assert_eq!(
22648            multi_buffer
22649                .read(cx)
22650                .snapshot(cx)
22651                .text_for_range(Point::new(1, 0)..Point::new(1, 4))
22652                .collect::<String>(),
22653            "bbbb"
22654        );
22655        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22656            selections.select_ranges(vec![Point::new(1, 0)..Point::new(1, 0)]);
22657        });
22658        editor.handle_input("B", window, cx);
22659    });
22660
22661    assert_eq!(
22662        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22663        "\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",
22664        "After unfolding the first buffer, its and 2nd buffer's text should be displayed"
22665    );
22666
22667    multi_buffer_editor.update(cx, |editor, cx| {
22668        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22669    });
22670    assert_eq!(
22671        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22672        "\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",
22673        "After unfolding the all buffers, all original text should be displayed"
22674    );
22675}
22676
22677#[gpui::test]
22678async fn test_folding_buffers_with_one_excerpt(cx: &mut TestAppContext) {
22679    init_test(cx, |_| {});
22680
22681    let sample_text_1 = "1111\n2222\n3333".to_string();
22682    let sample_text_2 = "4444\n5555\n6666".to_string();
22683    let sample_text_3 = "7777\n8888\n9999".to_string();
22684
22685    let fs = FakeFs::new(cx.executor());
22686    fs.insert_tree(
22687        path!("/a"),
22688        json!({
22689            "first.rs": sample_text_1,
22690            "second.rs": sample_text_2,
22691            "third.rs": sample_text_3,
22692        }),
22693    )
22694    .await;
22695    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22696    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22697    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22698    let worktree = project.update(cx, |project, cx| {
22699        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22700        assert_eq!(worktrees.len(), 1);
22701        worktrees.pop().unwrap()
22702    });
22703    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22704
22705    let buffer_1 = project
22706        .update(cx, |project, cx| {
22707            project.open_buffer((worktree_id, rel_path("first.rs")), cx)
22708        })
22709        .await
22710        .unwrap();
22711    let buffer_2 = project
22712        .update(cx, |project, cx| {
22713            project.open_buffer((worktree_id, rel_path("second.rs")), cx)
22714        })
22715        .await
22716        .unwrap();
22717    let buffer_3 = project
22718        .update(cx, |project, cx| {
22719            project.open_buffer((worktree_id, rel_path("third.rs")), cx)
22720        })
22721        .await
22722        .unwrap();
22723
22724    let multi_buffer = cx.new(|cx| {
22725        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22726        multi_buffer.push_excerpts(
22727            buffer_1.clone(),
22728            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22729            cx,
22730        );
22731        multi_buffer.push_excerpts(
22732            buffer_2.clone(),
22733            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22734            cx,
22735        );
22736        multi_buffer.push_excerpts(
22737            buffer_3.clone(),
22738            [ExcerptRange::new(Point::new(0, 0)..Point::new(3, 0))],
22739            cx,
22740        );
22741        multi_buffer
22742    });
22743
22744    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22745        Editor::new(
22746            EditorMode::full(),
22747            multi_buffer,
22748            Some(project.clone()),
22749            window,
22750            cx,
22751        )
22752    });
22753
22754    let full_text = "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999";
22755    assert_eq!(
22756        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22757        full_text,
22758    );
22759
22760    multi_buffer_editor.update(cx, |editor, cx| {
22761        editor.fold_buffer(buffer_1.read(cx).remote_id(), cx)
22762    });
22763    assert_eq!(
22764        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22765        "\n\n\n\n4444\n5555\n6666\n\n\n7777\n8888\n9999",
22766        "After folding the first buffer, its text should not be displayed"
22767    );
22768
22769    multi_buffer_editor.update(cx, |editor, cx| {
22770        editor.fold_buffer(buffer_2.read(cx).remote_id(), cx)
22771    });
22772
22773    assert_eq!(
22774        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22775        "\n\n\n\n\n\n7777\n8888\n9999",
22776        "After folding the second buffer, its text should not be displayed"
22777    );
22778
22779    multi_buffer_editor.update(cx, |editor, cx| {
22780        editor.fold_buffer(buffer_3.read(cx).remote_id(), cx)
22781    });
22782    assert_eq!(
22783        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22784        "\n\n\n\n\n",
22785        "After folding the third buffer, its text should not be displayed"
22786    );
22787
22788    multi_buffer_editor.update(cx, |editor, cx| {
22789        editor.unfold_buffer(buffer_2.read(cx).remote_id(), cx)
22790    });
22791    assert_eq!(
22792        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22793        "\n\n\n\n4444\n5555\n6666\n\n",
22794        "After unfolding the second buffer, its text should be displayed"
22795    );
22796
22797    multi_buffer_editor.update(cx, |editor, cx| {
22798        editor.unfold_buffer(buffer_1.read(cx).remote_id(), cx)
22799    });
22800    assert_eq!(
22801        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22802        "\n\n1111\n2222\n3333\n\n\n4444\n5555\n6666\n\n",
22803        "After unfolding the first buffer, its text should be displayed"
22804    );
22805
22806    multi_buffer_editor.update(cx, |editor, cx| {
22807        editor.unfold_buffer(buffer_3.read(cx).remote_id(), cx)
22808    });
22809    assert_eq!(
22810        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22811        full_text,
22812        "After unfolding all buffers, all original text should be displayed"
22813    );
22814}
22815
22816#[gpui::test]
22817async fn test_folding_buffer_when_multibuffer_has_only_one_excerpt(cx: &mut TestAppContext) {
22818    init_test(cx, |_| {});
22819
22820    let sample_text = "aaaa\nbbbb\ncccc\ndddd\neeee\nffff\ngggg\nhhhh\niiii\njjjj".to_string();
22821
22822    let fs = FakeFs::new(cx.executor());
22823    fs.insert_tree(
22824        path!("/a"),
22825        json!({
22826            "main.rs": sample_text,
22827        }),
22828    )
22829    .await;
22830    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
22831    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
22832    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
22833    let worktree = project.update(cx, |project, cx| {
22834        let mut worktrees = project.worktrees(cx).collect::<Vec<_>>();
22835        assert_eq!(worktrees.len(), 1);
22836        worktrees.pop().unwrap()
22837    });
22838    let worktree_id = worktree.update(cx, |worktree, _| worktree.id());
22839
22840    let buffer_1 = project
22841        .update(cx, |project, cx| {
22842            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
22843        })
22844        .await
22845        .unwrap();
22846
22847    let multi_buffer = cx.new(|cx| {
22848        let mut multi_buffer = MultiBuffer::new(ReadWrite);
22849        multi_buffer.push_excerpts(
22850            buffer_1.clone(),
22851            [ExcerptRange::new(
22852                Point::new(0, 0)
22853                    ..Point::new(
22854                        sample_text.chars().filter(|&c| c == '\n').count() as u32 + 1,
22855                        0,
22856                    ),
22857            )],
22858            cx,
22859        );
22860        multi_buffer
22861    });
22862    let multi_buffer_editor = cx.new_window_entity(|window, cx| {
22863        Editor::new(
22864            EditorMode::full(),
22865            multi_buffer,
22866            Some(project.clone()),
22867            window,
22868            cx,
22869        )
22870    });
22871
22872    let selection_range = Point::new(1, 0)..Point::new(2, 0);
22873    multi_buffer_editor.update_in(cx, |editor, window, cx| {
22874        enum TestHighlight {}
22875        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22876        let highlight_range = selection_range.clone().to_anchors(&multi_buffer_snapshot);
22877        editor.highlight_text::<TestHighlight>(
22878            vec![highlight_range.clone()],
22879            HighlightStyle::color(Hsla::green()),
22880            cx,
22881        );
22882        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22883            s.select_ranges(Some(highlight_range))
22884        });
22885    });
22886
22887    let full_text = format!("\n\n{sample_text}");
22888    assert_eq!(
22889        multi_buffer_editor.update(cx, |editor, cx| editor.display_text(cx)),
22890        full_text,
22891    );
22892}
22893
22894#[gpui::test]
22895async fn test_multi_buffer_navigation_with_folded_buffers(cx: &mut TestAppContext) {
22896    init_test(cx, |_| {});
22897    cx.update(|cx| {
22898        let default_key_bindings = settings::KeymapFile::load_asset_allow_partial_failure(
22899            "keymaps/default-linux.json",
22900            cx,
22901        )
22902        .unwrap();
22903        cx.bind_keys(default_key_bindings);
22904    });
22905
22906    let (editor, cx) = cx.add_window_view(|window, cx| {
22907        let multi_buffer = MultiBuffer::build_multi(
22908            [
22909                ("a0\nb0\nc0\nd0\ne0\n", vec![Point::row_range(0..2)]),
22910                ("a1\nb1\nc1\nd1\ne1\n", vec![Point::row_range(0..2)]),
22911                ("a2\nb2\nc2\nd2\ne2\n", vec![Point::row_range(0..2)]),
22912                ("a3\nb3\nc3\nd3\ne3\n", vec![Point::row_range(0..2)]),
22913            ],
22914            cx,
22915        );
22916        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
22917
22918        let buffer_ids = multi_buffer.read(cx).excerpt_buffer_ids();
22919        // fold all but the second buffer, so that we test navigating between two
22920        // adjacent folded buffers, as well as folded buffers at the start and
22921        // end the multibuffer
22922        editor.fold_buffer(buffer_ids[0], cx);
22923        editor.fold_buffer(buffer_ids[2], cx);
22924        editor.fold_buffer(buffer_ids[3], cx);
22925
22926        editor
22927    });
22928    cx.simulate_resize(size(px(1000.), px(1000.)));
22929
22930    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
22931    cx.assert_excerpts_with_selections(indoc! {"
22932        [EXCERPT]
22933        ˇ[FOLDED]
22934        [EXCERPT]
22935        a1
22936        b1
22937        [EXCERPT]
22938        [FOLDED]
22939        [EXCERPT]
22940        [FOLDED]
22941        "
22942    });
22943    cx.simulate_keystroke("down");
22944    cx.assert_excerpts_with_selections(indoc! {"
22945        [EXCERPT]
22946        [FOLDED]
22947        [EXCERPT]
22948        ˇa1
22949        b1
22950        [EXCERPT]
22951        [FOLDED]
22952        [EXCERPT]
22953        [FOLDED]
22954        "
22955    });
22956    cx.simulate_keystroke("down");
22957    cx.assert_excerpts_with_selections(indoc! {"
22958        [EXCERPT]
22959        [FOLDED]
22960        [EXCERPT]
22961        a1
22962        ˇb1
22963        [EXCERPT]
22964        [FOLDED]
22965        [EXCERPT]
22966        [FOLDED]
22967        "
22968    });
22969    cx.simulate_keystroke("down");
22970    cx.assert_excerpts_with_selections(indoc! {"
22971        [EXCERPT]
22972        [FOLDED]
22973        [EXCERPT]
22974        a1
22975        b1
22976        ˇ[EXCERPT]
22977        [FOLDED]
22978        [EXCERPT]
22979        [FOLDED]
22980        "
22981    });
22982    cx.simulate_keystroke("down");
22983    cx.assert_excerpts_with_selections(indoc! {"
22984        [EXCERPT]
22985        [FOLDED]
22986        [EXCERPT]
22987        a1
22988        b1
22989        [EXCERPT]
22990        ˇ[FOLDED]
22991        [EXCERPT]
22992        [FOLDED]
22993        "
22994    });
22995    for _ in 0..5 {
22996        cx.simulate_keystroke("down");
22997        cx.assert_excerpts_with_selections(indoc! {"
22998            [EXCERPT]
22999            [FOLDED]
23000            [EXCERPT]
23001            a1
23002            b1
23003            [EXCERPT]
23004            [FOLDED]
23005            [EXCERPT]
23006            ˇ[FOLDED]
23007            "
23008        });
23009    }
23010
23011    cx.simulate_keystroke("up");
23012    cx.assert_excerpts_with_selections(indoc! {"
23013        [EXCERPT]
23014        [FOLDED]
23015        [EXCERPT]
23016        a1
23017        b1
23018        [EXCERPT]
23019        ˇ[FOLDED]
23020        [EXCERPT]
23021        [FOLDED]
23022        "
23023    });
23024    cx.simulate_keystroke("up");
23025    cx.assert_excerpts_with_selections(indoc! {"
23026        [EXCERPT]
23027        [FOLDED]
23028        [EXCERPT]
23029        a1
23030        b1
23031        ˇ[EXCERPT]
23032        [FOLDED]
23033        [EXCERPT]
23034        [FOLDED]
23035        "
23036    });
23037    cx.simulate_keystroke("up");
23038    cx.assert_excerpts_with_selections(indoc! {"
23039        [EXCERPT]
23040        [FOLDED]
23041        [EXCERPT]
23042        a1
23043        ˇb1
23044        [EXCERPT]
23045        [FOLDED]
23046        [EXCERPT]
23047        [FOLDED]
23048        "
23049    });
23050    cx.simulate_keystroke("up");
23051    cx.assert_excerpts_with_selections(indoc! {"
23052        [EXCERPT]
23053        [FOLDED]
23054        [EXCERPT]
23055        ˇa1
23056        b1
23057        [EXCERPT]
23058        [FOLDED]
23059        [EXCERPT]
23060        [FOLDED]
23061        "
23062    });
23063    for _ in 0..5 {
23064        cx.simulate_keystroke("up");
23065        cx.assert_excerpts_with_selections(indoc! {"
23066            [EXCERPT]
23067            ˇ[FOLDED]
23068            [EXCERPT]
23069            a1
23070            b1
23071            [EXCERPT]
23072            [FOLDED]
23073            [EXCERPT]
23074            [FOLDED]
23075            "
23076        });
23077    }
23078}
23079
23080#[gpui::test]
23081async fn test_edit_prediction_text(cx: &mut TestAppContext) {
23082    init_test(cx, |_| {});
23083
23084    // Simple insertion
23085    assert_highlighted_edits(
23086        "Hello, world!",
23087        vec![(Point::new(0, 6)..Point::new(0, 6), " beautiful".into())],
23088        true,
23089        cx,
23090        |highlighted_edits, cx| {
23091            assert_eq!(highlighted_edits.text, "Hello, beautiful world!");
23092            assert_eq!(highlighted_edits.highlights.len(), 1);
23093            assert_eq!(highlighted_edits.highlights[0].0, 6..16);
23094            assert_eq!(
23095                highlighted_edits.highlights[0].1.background_color,
23096                Some(cx.theme().status().created_background)
23097            );
23098        },
23099    )
23100    .await;
23101
23102    // Replacement
23103    assert_highlighted_edits(
23104        "This is a test.",
23105        vec![(Point::new(0, 0)..Point::new(0, 4), "That".into())],
23106        false,
23107        cx,
23108        |highlighted_edits, cx| {
23109            assert_eq!(highlighted_edits.text, "That is a test.");
23110            assert_eq!(highlighted_edits.highlights.len(), 1);
23111            assert_eq!(highlighted_edits.highlights[0].0, 0..4);
23112            assert_eq!(
23113                highlighted_edits.highlights[0].1.background_color,
23114                Some(cx.theme().status().created_background)
23115            );
23116        },
23117    )
23118    .await;
23119
23120    // Multiple edits
23121    assert_highlighted_edits(
23122        "Hello, world!",
23123        vec![
23124            (Point::new(0, 0)..Point::new(0, 5), "Greetings".into()),
23125            (Point::new(0, 12)..Point::new(0, 12), " and universe".into()),
23126        ],
23127        false,
23128        cx,
23129        |highlighted_edits, cx| {
23130            assert_eq!(highlighted_edits.text, "Greetings, world and universe!");
23131            assert_eq!(highlighted_edits.highlights.len(), 2);
23132            assert_eq!(highlighted_edits.highlights[0].0, 0..9);
23133            assert_eq!(highlighted_edits.highlights[1].0, 16..29);
23134            assert_eq!(
23135                highlighted_edits.highlights[0].1.background_color,
23136                Some(cx.theme().status().created_background)
23137            );
23138            assert_eq!(
23139                highlighted_edits.highlights[1].1.background_color,
23140                Some(cx.theme().status().created_background)
23141            );
23142        },
23143    )
23144    .await;
23145
23146    // Multiple lines with edits
23147    assert_highlighted_edits(
23148        "First line\nSecond line\nThird line\nFourth line",
23149        vec![
23150            (Point::new(1, 7)..Point::new(1, 11), "modified".to_string()),
23151            (
23152                Point::new(2, 0)..Point::new(2, 10),
23153                "New third line".to_string(),
23154            ),
23155            (Point::new(3, 6)..Point::new(3, 6), " updated".to_string()),
23156        ],
23157        false,
23158        cx,
23159        |highlighted_edits, cx| {
23160            assert_eq!(
23161                highlighted_edits.text,
23162                "Second modified\nNew third line\nFourth updated line"
23163            );
23164            assert_eq!(highlighted_edits.highlights.len(), 3);
23165            assert_eq!(highlighted_edits.highlights[0].0, 7..15); // "modified"
23166            assert_eq!(highlighted_edits.highlights[1].0, 16..30); // "New third line"
23167            assert_eq!(highlighted_edits.highlights[2].0, 37..45); // " updated"
23168            for highlight in &highlighted_edits.highlights {
23169                assert_eq!(
23170                    highlight.1.background_color,
23171                    Some(cx.theme().status().created_background)
23172                );
23173            }
23174        },
23175    )
23176    .await;
23177}
23178
23179#[gpui::test]
23180async fn test_edit_prediction_text_with_deletions(cx: &mut TestAppContext) {
23181    init_test(cx, |_| {});
23182
23183    // Deletion
23184    assert_highlighted_edits(
23185        "Hello, world!",
23186        vec![(Point::new(0, 5)..Point::new(0, 11), "".to_string())],
23187        true,
23188        cx,
23189        |highlighted_edits, cx| {
23190            assert_eq!(highlighted_edits.text, "Hello, world!");
23191            assert_eq!(highlighted_edits.highlights.len(), 1);
23192            assert_eq!(highlighted_edits.highlights[0].0, 5..11);
23193            assert_eq!(
23194                highlighted_edits.highlights[0].1.background_color,
23195                Some(cx.theme().status().deleted_background)
23196            );
23197        },
23198    )
23199    .await;
23200
23201    // Insertion
23202    assert_highlighted_edits(
23203        "Hello, world!",
23204        vec![(Point::new(0, 6)..Point::new(0, 6), " digital".to_string())],
23205        true,
23206        cx,
23207        |highlighted_edits, cx| {
23208            assert_eq!(highlighted_edits.highlights.len(), 1);
23209            assert_eq!(highlighted_edits.highlights[0].0, 6..14);
23210            assert_eq!(
23211                highlighted_edits.highlights[0].1.background_color,
23212                Some(cx.theme().status().created_background)
23213            );
23214        },
23215    )
23216    .await;
23217}
23218
23219async fn assert_highlighted_edits(
23220    text: &str,
23221    edits: Vec<(Range<Point>, String)>,
23222    include_deletions: bool,
23223    cx: &mut TestAppContext,
23224    assertion_fn: impl Fn(HighlightedText, &App),
23225) {
23226    let window = cx.add_window(|window, cx| {
23227        let buffer = MultiBuffer::build_simple(text, cx);
23228        Editor::new(EditorMode::full(), buffer, None, window, cx)
23229    });
23230    let cx = &mut VisualTestContext::from_window(*window, cx);
23231
23232    let (buffer, snapshot) = window
23233        .update(cx, |editor, _window, cx| {
23234            (
23235                editor.buffer().clone(),
23236                editor.buffer().read(cx).snapshot(cx),
23237            )
23238        })
23239        .unwrap();
23240
23241    let edits = edits
23242        .into_iter()
23243        .map(|(range, edit)| {
23244            (
23245                snapshot.anchor_after(range.start)..snapshot.anchor_before(range.end),
23246                edit,
23247            )
23248        })
23249        .collect::<Vec<_>>();
23250
23251    let text_anchor_edits = edits
23252        .clone()
23253        .into_iter()
23254        .map(|(range, edit)| (range.start.text_anchor..range.end.text_anchor, edit.into()))
23255        .collect::<Vec<_>>();
23256
23257    let edit_preview = window
23258        .update(cx, |_, _window, cx| {
23259            buffer
23260                .read(cx)
23261                .as_singleton()
23262                .unwrap()
23263                .read(cx)
23264                .preview_edits(text_anchor_edits.into(), cx)
23265        })
23266        .unwrap()
23267        .await;
23268
23269    cx.update(|_window, cx| {
23270        let highlighted_edits = edit_prediction_edit_text(
23271            snapshot.as_singleton().unwrap().2,
23272            &edits,
23273            &edit_preview,
23274            include_deletions,
23275            cx,
23276        );
23277        assertion_fn(highlighted_edits, cx)
23278    });
23279}
23280
23281#[track_caller]
23282fn assert_breakpoint(
23283    breakpoints: &BTreeMap<Arc<Path>, Vec<SourceBreakpoint>>,
23284    path: &Arc<Path>,
23285    expected: Vec<(u32, Breakpoint)>,
23286) {
23287    if expected.is_empty() {
23288        assert!(!breakpoints.contains_key(path), "{}", path.display());
23289    } else {
23290        let mut breakpoint = breakpoints
23291            .get(path)
23292            .unwrap()
23293            .iter()
23294            .map(|breakpoint| {
23295                (
23296                    breakpoint.row,
23297                    Breakpoint {
23298                        message: breakpoint.message.clone(),
23299                        state: breakpoint.state,
23300                        condition: breakpoint.condition.clone(),
23301                        hit_condition: breakpoint.hit_condition.clone(),
23302                    },
23303                )
23304            })
23305            .collect::<Vec<_>>();
23306
23307        breakpoint.sort_by_key(|(cached_position, _)| *cached_position);
23308
23309        assert_eq!(expected, breakpoint);
23310    }
23311}
23312
23313fn add_log_breakpoint_at_cursor(
23314    editor: &mut Editor,
23315    log_message: &str,
23316    window: &mut Window,
23317    cx: &mut Context<Editor>,
23318) {
23319    let (anchor, bp) = editor
23320        .breakpoints_at_cursors(window, cx)
23321        .first()
23322        .and_then(|(anchor, bp)| bp.as_ref().map(|bp| (*anchor, bp.clone())))
23323        .unwrap_or_else(|| {
23324            let snapshot = editor.snapshot(window, cx);
23325            let cursor_position: Point =
23326                editor.selections.newest(&snapshot.display_snapshot).head();
23327
23328            let breakpoint_position = snapshot
23329                .buffer_snapshot()
23330                .anchor_before(Point::new(cursor_position.row, 0));
23331
23332            (breakpoint_position, Breakpoint::new_log(log_message))
23333        });
23334
23335    editor.edit_breakpoint_at_anchor(
23336        anchor,
23337        bp,
23338        BreakpointEditAction::EditLogMessage(log_message.into()),
23339        cx,
23340    );
23341}
23342
23343#[gpui::test]
23344async fn test_breakpoint_toggling(cx: &mut TestAppContext) {
23345    init_test(cx, |_| {});
23346
23347    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23348    let fs = FakeFs::new(cx.executor());
23349    fs.insert_tree(
23350        path!("/a"),
23351        json!({
23352            "main.rs": sample_text,
23353        }),
23354    )
23355    .await;
23356    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23357    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23358    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23359
23360    let fs = FakeFs::new(cx.executor());
23361    fs.insert_tree(
23362        path!("/a"),
23363        json!({
23364            "main.rs": sample_text,
23365        }),
23366    )
23367    .await;
23368    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23369    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23370    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23371    let worktree_id = workspace
23372        .update(cx, |workspace, _window, cx| {
23373            workspace.project().update(cx, |project, cx| {
23374                project.worktrees(cx).next().unwrap().read(cx).id()
23375            })
23376        })
23377        .unwrap();
23378
23379    let buffer = project
23380        .update(cx, |project, cx| {
23381            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23382        })
23383        .await
23384        .unwrap();
23385
23386    let (editor, cx) = cx.add_window_view(|window, cx| {
23387        Editor::new(
23388            EditorMode::full(),
23389            MultiBuffer::build_from_buffer(buffer, cx),
23390            Some(project.clone()),
23391            window,
23392            cx,
23393        )
23394    });
23395
23396    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23397    let abs_path = project.read_with(cx, |project, cx| {
23398        project
23399            .absolute_path(&project_path, cx)
23400            .map(Arc::from)
23401            .unwrap()
23402    });
23403
23404    // assert we can add breakpoint on the first line
23405    editor.update_in(cx, |editor, window, cx| {
23406        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23407        editor.move_to_end(&MoveToEnd, window, cx);
23408        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23409    });
23410
23411    let breakpoints = editor.update(cx, |editor, cx| {
23412        editor
23413            .breakpoint_store()
23414            .as_ref()
23415            .unwrap()
23416            .read(cx)
23417            .all_source_breakpoints(cx)
23418    });
23419
23420    assert_eq!(1, breakpoints.len());
23421    assert_breakpoint(
23422        &breakpoints,
23423        &abs_path,
23424        vec![
23425            (0, Breakpoint::new_standard()),
23426            (3, Breakpoint::new_standard()),
23427        ],
23428    );
23429
23430    editor.update_in(cx, |editor, window, cx| {
23431        editor.move_to_beginning(&MoveToBeginning, window, cx);
23432        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23433    });
23434
23435    let breakpoints = editor.update(cx, |editor, cx| {
23436        editor
23437            .breakpoint_store()
23438            .as_ref()
23439            .unwrap()
23440            .read(cx)
23441            .all_source_breakpoints(cx)
23442    });
23443
23444    assert_eq!(1, breakpoints.len());
23445    assert_breakpoint(
23446        &breakpoints,
23447        &abs_path,
23448        vec![(3, Breakpoint::new_standard())],
23449    );
23450
23451    editor.update_in(cx, |editor, window, cx| {
23452        editor.move_to_end(&MoveToEnd, window, cx);
23453        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23454    });
23455
23456    let breakpoints = editor.update(cx, |editor, cx| {
23457        editor
23458            .breakpoint_store()
23459            .as_ref()
23460            .unwrap()
23461            .read(cx)
23462            .all_source_breakpoints(cx)
23463    });
23464
23465    assert_eq!(0, breakpoints.len());
23466    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23467}
23468
23469#[gpui::test]
23470async fn test_log_breakpoint_editing(cx: &mut TestAppContext) {
23471    init_test(cx, |_| {});
23472
23473    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23474
23475    let fs = FakeFs::new(cx.executor());
23476    fs.insert_tree(
23477        path!("/a"),
23478        json!({
23479            "main.rs": sample_text,
23480        }),
23481    )
23482    .await;
23483    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23484    let (workspace, cx) =
23485        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
23486
23487    let worktree_id = workspace.update(cx, |workspace, cx| {
23488        workspace.project().update(cx, |project, cx| {
23489            project.worktrees(cx).next().unwrap().read(cx).id()
23490        })
23491    });
23492
23493    let buffer = project
23494        .update(cx, |project, cx| {
23495            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23496        })
23497        .await
23498        .unwrap();
23499
23500    let (editor, cx) = cx.add_window_view(|window, cx| {
23501        Editor::new(
23502            EditorMode::full(),
23503            MultiBuffer::build_from_buffer(buffer, cx),
23504            Some(project.clone()),
23505            window,
23506            cx,
23507        )
23508    });
23509
23510    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23511    let abs_path = project.read_with(cx, |project, cx| {
23512        project
23513            .absolute_path(&project_path, cx)
23514            .map(Arc::from)
23515            .unwrap()
23516    });
23517
23518    editor.update_in(cx, |editor, window, cx| {
23519        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23520    });
23521
23522    let breakpoints = editor.update(cx, |editor, cx| {
23523        editor
23524            .breakpoint_store()
23525            .as_ref()
23526            .unwrap()
23527            .read(cx)
23528            .all_source_breakpoints(cx)
23529    });
23530
23531    assert_breakpoint(
23532        &breakpoints,
23533        &abs_path,
23534        vec![(0, Breakpoint::new_log("hello world"))],
23535    );
23536
23537    // Removing a log message from a log breakpoint should remove it
23538    editor.update_in(cx, |editor, window, cx| {
23539        add_log_breakpoint_at_cursor(editor, "", window, cx);
23540    });
23541
23542    let breakpoints = editor.update(cx, |editor, cx| {
23543        editor
23544            .breakpoint_store()
23545            .as_ref()
23546            .unwrap()
23547            .read(cx)
23548            .all_source_breakpoints(cx)
23549    });
23550
23551    assert_breakpoint(&breakpoints, &abs_path, vec![]);
23552
23553    editor.update_in(cx, |editor, window, cx| {
23554        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23555        editor.move_to_end(&MoveToEnd, window, cx);
23556        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23557        // Not adding a log message to a standard breakpoint shouldn't remove it
23558        add_log_breakpoint_at_cursor(editor, "", window, cx);
23559    });
23560
23561    let breakpoints = editor.update(cx, |editor, cx| {
23562        editor
23563            .breakpoint_store()
23564            .as_ref()
23565            .unwrap()
23566            .read(cx)
23567            .all_source_breakpoints(cx)
23568    });
23569
23570    assert_breakpoint(
23571        &breakpoints,
23572        &abs_path,
23573        vec![
23574            (0, Breakpoint::new_standard()),
23575            (3, Breakpoint::new_standard()),
23576        ],
23577    );
23578
23579    editor.update_in(cx, |editor, window, cx| {
23580        add_log_breakpoint_at_cursor(editor, "hello world", window, cx);
23581    });
23582
23583    let breakpoints = editor.update(cx, |editor, cx| {
23584        editor
23585            .breakpoint_store()
23586            .as_ref()
23587            .unwrap()
23588            .read(cx)
23589            .all_source_breakpoints(cx)
23590    });
23591
23592    assert_breakpoint(
23593        &breakpoints,
23594        &abs_path,
23595        vec![
23596            (0, Breakpoint::new_standard()),
23597            (3, Breakpoint::new_log("hello world")),
23598        ],
23599    );
23600
23601    editor.update_in(cx, |editor, window, cx| {
23602        add_log_breakpoint_at_cursor(editor, "hello Earth!!", window, cx);
23603    });
23604
23605    let breakpoints = editor.update(cx, |editor, cx| {
23606        editor
23607            .breakpoint_store()
23608            .as_ref()
23609            .unwrap()
23610            .read(cx)
23611            .all_source_breakpoints(cx)
23612    });
23613
23614    assert_breakpoint(
23615        &breakpoints,
23616        &abs_path,
23617        vec![
23618            (0, Breakpoint::new_standard()),
23619            (3, Breakpoint::new_log("hello Earth!!")),
23620        ],
23621    );
23622}
23623
23624/// This also tests that Editor::breakpoint_at_cursor_head is working properly
23625/// we had some issues where we wouldn't find a breakpoint at Point {row: 0, col: 0}
23626/// or when breakpoints were placed out of order. This tests for a regression too
23627#[gpui::test]
23628async fn test_breakpoint_enabling_and_disabling(cx: &mut TestAppContext) {
23629    init_test(cx, |_| {});
23630
23631    let sample_text = "First line\nSecond line\nThird line\nFourth line".to_string();
23632    let fs = FakeFs::new(cx.executor());
23633    fs.insert_tree(
23634        path!("/a"),
23635        json!({
23636            "main.rs": sample_text,
23637        }),
23638    )
23639    .await;
23640    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23641    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23642    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23643
23644    let fs = FakeFs::new(cx.executor());
23645    fs.insert_tree(
23646        path!("/a"),
23647        json!({
23648            "main.rs": sample_text,
23649        }),
23650    )
23651    .await;
23652    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
23653    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23654    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23655    let worktree_id = workspace
23656        .update(cx, |workspace, _window, cx| {
23657            workspace.project().update(cx, |project, cx| {
23658                project.worktrees(cx).next().unwrap().read(cx).id()
23659            })
23660        })
23661        .unwrap();
23662
23663    let buffer = project
23664        .update(cx, |project, cx| {
23665            project.open_buffer((worktree_id, rel_path("main.rs")), cx)
23666        })
23667        .await
23668        .unwrap();
23669
23670    let (editor, cx) = cx.add_window_view(|window, cx| {
23671        Editor::new(
23672            EditorMode::full(),
23673            MultiBuffer::build_from_buffer(buffer, cx),
23674            Some(project.clone()),
23675            window,
23676            cx,
23677        )
23678    });
23679
23680    let project_path = editor.update(cx, |editor, cx| editor.project_path(cx).unwrap());
23681    let abs_path = project.read_with(cx, |project, cx| {
23682        project
23683            .absolute_path(&project_path, cx)
23684            .map(Arc::from)
23685            .unwrap()
23686    });
23687
23688    // assert we can add breakpoint on the first line
23689    editor.update_in(cx, |editor, window, cx| {
23690        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23691        editor.move_to_end(&MoveToEnd, window, cx);
23692        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23693        editor.move_up(&MoveUp, window, cx);
23694        editor.toggle_breakpoint(&actions::ToggleBreakpoint, window, cx);
23695    });
23696
23697    let breakpoints = editor.update(cx, |editor, cx| {
23698        editor
23699            .breakpoint_store()
23700            .as_ref()
23701            .unwrap()
23702            .read(cx)
23703            .all_source_breakpoints(cx)
23704    });
23705
23706    assert_eq!(1, breakpoints.len());
23707    assert_breakpoint(
23708        &breakpoints,
23709        &abs_path,
23710        vec![
23711            (0, Breakpoint::new_standard()),
23712            (2, Breakpoint::new_standard()),
23713            (3, Breakpoint::new_standard()),
23714        ],
23715    );
23716
23717    editor.update_in(cx, |editor, window, cx| {
23718        editor.move_to_beginning(&MoveToBeginning, window, cx);
23719        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23720        editor.move_to_end(&MoveToEnd, window, cx);
23721        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23722        // Disabling a breakpoint that doesn't exist should do nothing
23723        editor.move_up(&MoveUp, window, cx);
23724        editor.move_up(&MoveUp, window, cx);
23725        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23726    });
23727
23728    let breakpoints = editor.update(cx, |editor, cx| {
23729        editor
23730            .breakpoint_store()
23731            .as_ref()
23732            .unwrap()
23733            .read(cx)
23734            .all_source_breakpoints(cx)
23735    });
23736
23737    let disable_breakpoint = {
23738        let mut bp = Breakpoint::new_standard();
23739        bp.state = BreakpointState::Disabled;
23740        bp
23741    };
23742
23743    assert_eq!(1, breakpoints.len());
23744    assert_breakpoint(
23745        &breakpoints,
23746        &abs_path,
23747        vec![
23748            (0, disable_breakpoint.clone()),
23749            (2, Breakpoint::new_standard()),
23750            (3, disable_breakpoint.clone()),
23751        ],
23752    );
23753
23754    editor.update_in(cx, |editor, window, cx| {
23755        editor.move_to_beginning(&MoveToBeginning, window, cx);
23756        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23757        editor.move_to_end(&MoveToEnd, window, cx);
23758        editor.enable_breakpoint(&actions::EnableBreakpoint, window, cx);
23759        editor.move_up(&MoveUp, window, cx);
23760        editor.disable_breakpoint(&actions::DisableBreakpoint, window, cx);
23761    });
23762
23763    let breakpoints = editor.update(cx, |editor, cx| {
23764        editor
23765            .breakpoint_store()
23766            .as_ref()
23767            .unwrap()
23768            .read(cx)
23769            .all_source_breakpoints(cx)
23770    });
23771
23772    assert_eq!(1, breakpoints.len());
23773    assert_breakpoint(
23774        &breakpoints,
23775        &abs_path,
23776        vec![
23777            (0, Breakpoint::new_standard()),
23778            (2, disable_breakpoint),
23779            (3, Breakpoint::new_standard()),
23780        ],
23781    );
23782}
23783
23784#[gpui::test]
23785async fn test_rename_with_duplicate_edits(cx: &mut TestAppContext) {
23786    init_test(cx, |_| {});
23787    let capabilities = lsp::ServerCapabilities {
23788        rename_provider: Some(lsp::OneOf::Right(lsp::RenameOptions {
23789            prepare_provider: Some(true),
23790            work_done_progress_options: Default::default(),
23791        })),
23792        ..Default::default()
23793    };
23794    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23795
23796    cx.set_state(indoc! {"
23797        struct Fˇoo {}
23798    "});
23799
23800    cx.update_editor(|editor, _, cx| {
23801        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23802        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23803        editor.highlight_background::<DocumentHighlightRead>(
23804            &[highlight_range],
23805            |theme| theme.colors().editor_document_highlight_read_background,
23806            cx,
23807        );
23808    });
23809
23810    let mut prepare_rename_handler = cx
23811        .set_request_handler::<lsp::request::PrepareRenameRequest, _, _>(
23812            move |_, _, _| async move {
23813                Ok(Some(lsp::PrepareRenameResponse::Range(lsp::Range {
23814                    start: lsp::Position {
23815                        line: 0,
23816                        character: 7,
23817                    },
23818                    end: lsp::Position {
23819                        line: 0,
23820                        character: 10,
23821                    },
23822                })))
23823            },
23824        );
23825    let prepare_rename_task = cx
23826        .update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23827        .expect("Prepare rename was not started");
23828    prepare_rename_handler.next().await.unwrap();
23829    prepare_rename_task.await.expect("Prepare rename failed");
23830
23831    let mut rename_handler =
23832        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23833            let edit = lsp::TextEdit {
23834                range: lsp::Range {
23835                    start: lsp::Position {
23836                        line: 0,
23837                        character: 7,
23838                    },
23839                    end: lsp::Position {
23840                        line: 0,
23841                        character: 10,
23842                    },
23843                },
23844                new_text: "FooRenamed".to_string(),
23845            };
23846            Ok(Some(lsp::WorkspaceEdit::new(
23847                // Specify the same edit twice
23848                std::collections::HashMap::from_iter(Some((url, vec![edit.clone(), edit]))),
23849            )))
23850        });
23851    let rename_task = cx
23852        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23853        .expect("Confirm rename was not started");
23854    rename_handler.next().await.unwrap();
23855    rename_task.await.expect("Confirm rename failed");
23856    cx.run_until_parked();
23857
23858    // Despite two edits, only one is actually applied as those are identical
23859    cx.assert_editor_state(indoc! {"
23860        struct FooRenamedˇ {}
23861    "});
23862}
23863
23864#[gpui::test]
23865async fn test_rename_without_prepare(cx: &mut TestAppContext) {
23866    init_test(cx, |_| {});
23867    // These capabilities indicate that the server does not support prepare rename.
23868    let capabilities = lsp::ServerCapabilities {
23869        rename_provider: Some(lsp::OneOf::Left(true)),
23870        ..Default::default()
23871    };
23872    let mut cx = EditorLspTestContext::new_rust(capabilities, cx).await;
23873
23874    cx.set_state(indoc! {"
23875        struct Fˇoo {}
23876    "});
23877
23878    cx.update_editor(|editor, _window, cx| {
23879        let highlight_range = Point::new(0, 7)..Point::new(0, 10);
23880        let highlight_range = highlight_range.to_anchors(&editor.buffer().read(cx).snapshot(cx));
23881        editor.highlight_background::<DocumentHighlightRead>(
23882            &[highlight_range],
23883            |theme| theme.colors().editor_document_highlight_read_background,
23884            cx,
23885        );
23886    });
23887
23888    cx.update_editor(|e, window, cx| e.rename(&Rename, window, cx))
23889        .expect("Prepare rename was not started")
23890        .await
23891        .expect("Prepare rename failed");
23892
23893    let mut rename_handler =
23894        cx.set_request_handler::<lsp::request::Rename, _, _>(move |url, _, _| async move {
23895            let edit = lsp::TextEdit {
23896                range: lsp::Range {
23897                    start: lsp::Position {
23898                        line: 0,
23899                        character: 7,
23900                    },
23901                    end: lsp::Position {
23902                        line: 0,
23903                        character: 10,
23904                    },
23905                },
23906                new_text: "FooRenamed".to_string(),
23907            };
23908            Ok(Some(lsp::WorkspaceEdit::new(
23909                std::collections::HashMap::from_iter(Some((url, vec![edit]))),
23910            )))
23911        });
23912    let rename_task = cx
23913        .update_editor(|e, window, cx| e.confirm_rename(&ConfirmRename, window, cx))
23914        .expect("Confirm rename was not started");
23915    rename_handler.next().await.unwrap();
23916    rename_task.await.expect("Confirm rename failed");
23917    cx.run_until_parked();
23918
23919    // Correct range is renamed, as `surrounding_word` is used to find it.
23920    cx.assert_editor_state(indoc! {"
23921        struct FooRenamedˇ {}
23922    "});
23923}
23924
23925#[gpui::test]
23926async fn test_tree_sitter_brackets_newline_insertion(cx: &mut TestAppContext) {
23927    init_test(cx, |_| {});
23928    let mut cx = EditorTestContext::new(cx).await;
23929
23930    let language = Arc::new(
23931        Language::new(
23932            LanguageConfig::default(),
23933            Some(tree_sitter_html::LANGUAGE.into()),
23934        )
23935        .with_brackets_query(
23936            r#"
23937            ("<" @open "/>" @close)
23938            ("</" @open ">" @close)
23939            ("<" @open ">" @close)
23940            ("\"" @open "\"" @close)
23941            ((element (start_tag) @open (end_tag) @close) (#set! newline.only))
23942        "#,
23943        )
23944        .unwrap(),
23945    );
23946    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
23947
23948    cx.set_state(indoc! {"
23949        <span>ˇ</span>
23950    "});
23951    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23952    cx.assert_editor_state(indoc! {"
23953        <span>
23954        ˇ
23955        </span>
23956    "});
23957
23958    cx.set_state(indoc! {"
23959        <span><span></span>ˇ</span>
23960    "});
23961    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23962    cx.assert_editor_state(indoc! {"
23963        <span><span></span>
23964        ˇ</span>
23965    "});
23966
23967    cx.set_state(indoc! {"
23968        <span>ˇ
23969        </span>
23970    "});
23971    cx.update_editor(|e, window, cx| e.newline(&Newline, window, cx));
23972    cx.assert_editor_state(indoc! {"
23973        <span>
23974        ˇ
23975        </span>
23976    "});
23977}
23978
23979#[gpui::test(iterations = 10)]
23980async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContext) {
23981    init_test(cx, |_| {});
23982
23983    let fs = FakeFs::new(cx.executor());
23984    fs.insert_tree(
23985        path!("/dir"),
23986        json!({
23987            "a.ts": "a",
23988        }),
23989    )
23990    .await;
23991
23992    let project = Project::test(fs, [path!("/dir").as_ref()], cx).await;
23993    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
23994    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
23995
23996    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
23997    language_registry.add(Arc::new(Language::new(
23998        LanguageConfig {
23999            name: "TypeScript".into(),
24000            matcher: LanguageMatcher {
24001                path_suffixes: vec!["ts".to_string()],
24002                ..Default::default()
24003            },
24004            ..Default::default()
24005        },
24006        Some(tree_sitter_typescript::LANGUAGE_TYPESCRIPT.into()),
24007    )));
24008    let mut fake_language_servers = language_registry.register_fake_lsp(
24009        "TypeScript",
24010        FakeLspAdapter {
24011            capabilities: lsp::ServerCapabilities {
24012                code_lens_provider: Some(lsp::CodeLensOptions {
24013                    resolve_provider: Some(true),
24014                }),
24015                execute_command_provider: Some(lsp::ExecuteCommandOptions {
24016                    commands: vec!["_the/command".to_string()],
24017                    ..lsp::ExecuteCommandOptions::default()
24018                }),
24019                ..lsp::ServerCapabilities::default()
24020            },
24021            ..FakeLspAdapter::default()
24022        },
24023    );
24024
24025    let editor = workspace
24026        .update(cx, |workspace, window, cx| {
24027            workspace.open_abs_path(
24028                PathBuf::from(path!("/dir/a.ts")),
24029                OpenOptions::default(),
24030                window,
24031                cx,
24032            )
24033        })
24034        .unwrap()
24035        .await
24036        .unwrap()
24037        .downcast::<Editor>()
24038        .unwrap();
24039    cx.executor().run_until_parked();
24040
24041    let fake_server = fake_language_servers.next().await.unwrap();
24042
24043    let buffer = editor.update(cx, |editor, cx| {
24044        editor
24045            .buffer()
24046            .read(cx)
24047            .as_singleton()
24048            .expect("have opened a single file by path")
24049    });
24050
24051    let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
24052    let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
24053    drop(buffer_snapshot);
24054    let actions = cx
24055        .update_window(*workspace, |_, window, cx| {
24056            project.code_actions(&buffer, anchor..anchor, window, cx)
24057        })
24058        .unwrap();
24059
24060    fake_server
24061        .set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24062            Ok(Some(vec![
24063                lsp::CodeLens {
24064                    range: lsp::Range::default(),
24065                    command: Some(lsp::Command {
24066                        title: "Code lens command".to_owned(),
24067                        command: "_the/command".to_owned(),
24068                        arguments: None,
24069                    }),
24070                    data: None,
24071                },
24072                lsp::CodeLens {
24073                    range: lsp::Range::default(),
24074                    command: Some(lsp::Command {
24075                        title: "Command not in capabilities".to_owned(),
24076                        command: "not in capabilities".to_owned(),
24077                        arguments: None,
24078                    }),
24079                    data: None,
24080                },
24081                lsp::CodeLens {
24082                    range: lsp::Range {
24083                        start: lsp::Position {
24084                            line: 1,
24085                            character: 1,
24086                        },
24087                        end: lsp::Position {
24088                            line: 1,
24089                            character: 1,
24090                        },
24091                    },
24092                    command: Some(lsp::Command {
24093                        title: "Command not in range".to_owned(),
24094                        command: "_the/command".to_owned(),
24095                        arguments: None,
24096                    }),
24097                    data: None,
24098                },
24099            ]))
24100        })
24101        .next()
24102        .await;
24103
24104    let actions = actions.await.unwrap();
24105    assert_eq!(
24106        actions.len(),
24107        1,
24108        "Should have only one valid action for the 0..0 range, got: {actions:#?}"
24109    );
24110    let action = actions[0].clone();
24111    let apply = project.update(cx, |project, cx| {
24112        project.apply_code_action(buffer.clone(), action, true, cx)
24113    });
24114
24115    // Resolving the code action does not populate its edits. In absence of
24116    // edits, we must execute the given command.
24117    fake_server.set_request_handler::<lsp::request::CodeLensResolve, _, _>(
24118        |mut lens, _| async move {
24119            let lens_command = lens.command.as_mut().expect("should have a command");
24120            assert_eq!(lens_command.title, "Code lens command");
24121            lens_command.arguments = Some(vec![json!("the-argument")]);
24122            Ok(lens)
24123        },
24124    );
24125
24126    // While executing the command, the language server sends the editor
24127    // a `workspaceEdit` request.
24128    fake_server
24129        .set_request_handler::<lsp::request::ExecuteCommand, _, _>({
24130            let fake = fake_server.clone();
24131            move |params, _| {
24132                assert_eq!(params.command, "_the/command");
24133                let fake = fake.clone();
24134                async move {
24135                    fake.server
24136                        .request::<lsp::request::ApplyWorkspaceEdit>(
24137                            lsp::ApplyWorkspaceEditParams {
24138                                label: None,
24139                                edit: lsp::WorkspaceEdit {
24140                                    changes: Some(
24141                                        [(
24142                                            lsp::Uri::from_file_path(path!("/dir/a.ts")).unwrap(),
24143                                            vec![lsp::TextEdit {
24144                                                range: lsp::Range::new(
24145                                                    lsp::Position::new(0, 0),
24146                                                    lsp::Position::new(0, 0),
24147                                                ),
24148                                                new_text: "X".into(),
24149                                            }],
24150                                        )]
24151                                        .into_iter()
24152                                        .collect(),
24153                                    ),
24154                                    ..lsp::WorkspaceEdit::default()
24155                                },
24156                            },
24157                        )
24158                        .await
24159                        .into_response()
24160                        .unwrap();
24161                    Ok(Some(json!(null)))
24162                }
24163            }
24164        })
24165        .next()
24166        .await;
24167
24168    // Applying the code lens command returns a project transaction containing the edits
24169    // sent by the language server in its `workspaceEdit` request.
24170    let transaction = apply.await.unwrap();
24171    assert!(transaction.0.contains_key(&buffer));
24172    buffer.update(cx, |buffer, cx| {
24173        assert_eq!(buffer.text(), "Xa");
24174        buffer.undo(cx);
24175        assert_eq!(buffer.text(), "a");
24176    });
24177
24178    let actions_after_edits = cx
24179        .update_window(*workspace, |_, window, cx| {
24180            project.code_actions(&buffer, anchor..anchor, window, cx)
24181        })
24182        .unwrap()
24183        .await
24184        .unwrap();
24185    assert_eq!(
24186        actions, actions_after_edits,
24187        "For the same selection, same code lens actions should be returned"
24188    );
24189
24190    let _responses =
24191        fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
24192            panic!("No more code lens requests are expected");
24193        });
24194    editor.update_in(cx, |editor, window, cx| {
24195        editor.select_all(&SelectAll, window, cx);
24196    });
24197    cx.executor().run_until_parked();
24198    let new_actions = cx
24199        .update_window(*workspace, |_, window, cx| {
24200            project.code_actions(&buffer, anchor..anchor, window, cx)
24201        })
24202        .unwrap()
24203        .await
24204        .unwrap();
24205    assert_eq!(
24206        actions, new_actions,
24207        "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
24208    );
24209}
24210
24211#[gpui::test]
24212async fn test_editor_restore_data_different_in_panes(cx: &mut TestAppContext) {
24213    init_test(cx, |_| {});
24214
24215    let fs = FakeFs::new(cx.executor());
24216    let main_text = r#"fn main() {
24217println!("1");
24218println!("2");
24219println!("3");
24220println!("4");
24221println!("5");
24222}"#;
24223    let lib_text = "mod foo {}";
24224    fs.insert_tree(
24225        path!("/a"),
24226        json!({
24227            "lib.rs": lib_text,
24228            "main.rs": main_text,
24229        }),
24230    )
24231    .await;
24232
24233    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24234    let (workspace, cx) =
24235        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24236    let worktree_id = workspace.update(cx, |workspace, cx| {
24237        workspace.project().update(cx, |project, cx| {
24238            project.worktrees(cx).next().unwrap().read(cx).id()
24239        })
24240    });
24241
24242    let expected_ranges = vec![
24243        Point::new(0, 0)..Point::new(0, 0),
24244        Point::new(1, 0)..Point::new(1, 1),
24245        Point::new(2, 0)..Point::new(2, 2),
24246        Point::new(3, 0)..Point::new(3, 3),
24247    ];
24248
24249    let pane_1 = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24250    let editor_1 = workspace
24251        .update_in(cx, |workspace, window, cx| {
24252            workspace.open_path(
24253                (worktree_id, rel_path("main.rs")),
24254                Some(pane_1.downgrade()),
24255                true,
24256                window,
24257                cx,
24258            )
24259        })
24260        .unwrap()
24261        .await
24262        .downcast::<Editor>()
24263        .unwrap();
24264    pane_1.update(cx, |pane, cx| {
24265        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24266        open_editor.update(cx, |editor, cx| {
24267            assert_eq!(
24268                editor.display_text(cx),
24269                main_text,
24270                "Original main.rs text on initial open",
24271            );
24272            assert_eq!(
24273                editor
24274                    .selections
24275                    .all::<Point>(&editor.display_snapshot(cx))
24276                    .into_iter()
24277                    .map(|s| s.range())
24278                    .collect::<Vec<_>>(),
24279                vec![Point::zero()..Point::zero()],
24280                "Default selections on initial open",
24281            );
24282        })
24283    });
24284    editor_1.update_in(cx, |editor, window, cx| {
24285        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24286            s.select_ranges(expected_ranges.clone());
24287        });
24288    });
24289
24290    let pane_2 = workspace.update_in(cx, |workspace, window, cx| {
24291        workspace.split_pane(pane_1.clone(), SplitDirection::Right, window, cx)
24292    });
24293    let editor_2 = workspace
24294        .update_in(cx, |workspace, window, cx| {
24295            workspace.open_path(
24296                (worktree_id, rel_path("main.rs")),
24297                Some(pane_2.downgrade()),
24298                true,
24299                window,
24300                cx,
24301            )
24302        })
24303        .unwrap()
24304        .await
24305        .downcast::<Editor>()
24306        .unwrap();
24307    pane_2.update(cx, |pane, cx| {
24308        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24309        open_editor.update(cx, |editor, cx| {
24310            assert_eq!(
24311                editor.display_text(cx),
24312                main_text,
24313                "Original main.rs text on initial open in another panel",
24314            );
24315            assert_eq!(
24316                editor
24317                    .selections
24318                    .all::<Point>(&editor.display_snapshot(cx))
24319                    .into_iter()
24320                    .map(|s| s.range())
24321                    .collect::<Vec<_>>(),
24322                vec![Point::zero()..Point::zero()],
24323                "Default selections on initial open in another panel",
24324            );
24325        })
24326    });
24327
24328    editor_2.update_in(cx, |editor, window, cx| {
24329        editor.fold_ranges(expected_ranges.clone(), false, window, cx);
24330    });
24331
24332    let _other_editor_1 = workspace
24333        .update_in(cx, |workspace, window, cx| {
24334            workspace.open_path(
24335                (worktree_id, rel_path("lib.rs")),
24336                Some(pane_1.downgrade()),
24337                true,
24338                window,
24339                cx,
24340            )
24341        })
24342        .unwrap()
24343        .await
24344        .downcast::<Editor>()
24345        .unwrap();
24346    pane_1
24347        .update_in(cx, |pane, window, cx| {
24348            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24349        })
24350        .await
24351        .unwrap();
24352    drop(editor_1);
24353    pane_1.update(cx, |pane, cx| {
24354        pane.active_item()
24355            .unwrap()
24356            .downcast::<Editor>()
24357            .unwrap()
24358            .update(cx, |editor, cx| {
24359                assert_eq!(
24360                    editor.display_text(cx),
24361                    lib_text,
24362                    "Other file should be open and active",
24363                );
24364            });
24365        assert_eq!(pane.items().count(), 1, "No other editors should be open");
24366    });
24367
24368    let _other_editor_2 = workspace
24369        .update_in(cx, |workspace, window, cx| {
24370            workspace.open_path(
24371                (worktree_id, rel_path("lib.rs")),
24372                Some(pane_2.downgrade()),
24373                true,
24374                window,
24375                cx,
24376            )
24377        })
24378        .unwrap()
24379        .await
24380        .downcast::<Editor>()
24381        .unwrap();
24382    pane_2
24383        .update_in(cx, |pane, window, cx| {
24384            pane.close_other_items(&CloseOtherItems::default(), None, window, cx)
24385        })
24386        .await
24387        .unwrap();
24388    drop(editor_2);
24389    pane_2.update(cx, |pane, cx| {
24390        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24391        open_editor.update(cx, |editor, cx| {
24392            assert_eq!(
24393                editor.display_text(cx),
24394                lib_text,
24395                "Other file should be open and active in another panel too",
24396            );
24397        });
24398        assert_eq!(
24399            pane.items().count(),
24400            1,
24401            "No other editors should be open in another pane",
24402        );
24403    });
24404
24405    let _editor_1_reopened = workspace
24406        .update_in(cx, |workspace, window, cx| {
24407            workspace.open_path(
24408                (worktree_id, rel_path("main.rs")),
24409                Some(pane_1.downgrade()),
24410                true,
24411                window,
24412                cx,
24413            )
24414        })
24415        .unwrap()
24416        .await
24417        .downcast::<Editor>()
24418        .unwrap();
24419    let _editor_2_reopened = workspace
24420        .update_in(cx, |workspace, window, cx| {
24421            workspace.open_path(
24422                (worktree_id, rel_path("main.rs")),
24423                Some(pane_2.downgrade()),
24424                true,
24425                window,
24426                cx,
24427            )
24428        })
24429        .unwrap()
24430        .await
24431        .downcast::<Editor>()
24432        .unwrap();
24433    pane_1.update(cx, |pane, cx| {
24434        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24435        open_editor.update(cx, |editor, cx| {
24436            assert_eq!(
24437                editor.display_text(cx),
24438                main_text,
24439                "Previous editor in the 1st panel had no extra text manipulations and should get none on reopen",
24440            );
24441            assert_eq!(
24442                editor
24443                    .selections
24444                    .all::<Point>(&editor.display_snapshot(cx))
24445                    .into_iter()
24446                    .map(|s| s.range())
24447                    .collect::<Vec<_>>(),
24448                expected_ranges,
24449                "Previous editor in the 1st panel had selections and should get them restored on reopen",
24450            );
24451        })
24452    });
24453    pane_2.update(cx, |pane, cx| {
24454        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24455        open_editor.update(cx, |editor, cx| {
24456            assert_eq!(
24457                editor.display_text(cx),
24458                r#"fn main() {
24459⋯rintln!("1");
24460⋯intln!("2");
24461⋯ntln!("3");
24462println!("4");
24463println!("5");
24464}"#,
24465                "Previous editor in the 2nd pane had folds and should restore those on reopen in the same pane",
24466            );
24467            assert_eq!(
24468                editor
24469                    .selections
24470                    .all::<Point>(&editor.display_snapshot(cx))
24471                    .into_iter()
24472                    .map(|s| s.range())
24473                    .collect::<Vec<_>>(),
24474                vec![Point::zero()..Point::zero()],
24475                "Previous editor in the 2nd pane had no selections changed hence should restore none",
24476            );
24477        })
24478    });
24479}
24480
24481#[gpui::test]
24482async fn test_editor_does_not_restore_data_when_turned_off(cx: &mut TestAppContext) {
24483    init_test(cx, |_| {});
24484
24485    let fs = FakeFs::new(cx.executor());
24486    let main_text = r#"fn main() {
24487println!("1");
24488println!("2");
24489println!("3");
24490println!("4");
24491println!("5");
24492}"#;
24493    let lib_text = "mod foo {}";
24494    fs.insert_tree(
24495        path!("/a"),
24496        json!({
24497            "lib.rs": lib_text,
24498            "main.rs": main_text,
24499        }),
24500    )
24501    .await;
24502
24503    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
24504    let (workspace, cx) =
24505        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24506    let worktree_id = workspace.update(cx, |workspace, cx| {
24507        workspace.project().update(cx, |project, cx| {
24508            project.worktrees(cx).next().unwrap().read(cx).id()
24509        })
24510    });
24511
24512    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24513    let editor = workspace
24514        .update_in(cx, |workspace, window, cx| {
24515            workspace.open_path(
24516                (worktree_id, rel_path("main.rs")),
24517                Some(pane.downgrade()),
24518                true,
24519                window,
24520                cx,
24521            )
24522        })
24523        .unwrap()
24524        .await
24525        .downcast::<Editor>()
24526        .unwrap();
24527    pane.update(cx, |pane, cx| {
24528        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24529        open_editor.update(cx, |editor, cx| {
24530            assert_eq!(
24531                editor.display_text(cx),
24532                main_text,
24533                "Original main.rs text on initial open",
24534            );
24535        })
24536    });
24537    editor.update_in(cx, |editor, window, cx| {
24538        editor.fold_ranges(vec![Point::new(0, 0)..Point::new(0, 0)], false, window, cx);
24539    });
24540
24541    cx.update_global(|store: &mut SettingsStore, cx| {
24542        store.update_user_settings(cx, |s| {
24543            s.workspace.restore_on_file_reopen = Some(false);
24544        });
24545    });
24546    editor.update_in(cx, |editor, window, cx| {
24547        editor.fold_ranges(
24548            vec![
24549                Point::new(1, 0)..Point::new(1, 1),
24550                Point::new(2, 0)..Point::new(2, 2),
24551                Point::new(3, 0)..Point::new(3, 3),
24552            ],
24553            false,
24554            window,
24555            cx,
24556        );
24557    });
24558    pane.update_in(cx, |pane, window, cx| {
24559        pane.close_all_items(&CloseAllItems::default(), window, cx)
24560    })
24561    .await
24562    .unwrap();
24563    pane.update(cx, |pane, _| {
24564        assert!(pane.active_item().is_none());
24565    });
24566    cx.update_global(|store: &mut SettingsStore, cx| {
24567        store.update_user_settings(cx, |s| {
24568            s.workspace.restore_on_file_reopen = Some(true);
24569        });
24570    });
24571
24572    let _editor_reopened = workspace
24573        .update_in(cx, |workspace, window, cx| {
24574            workspace.open_path(
24575                (worktree_id, rel_path("main.rs")),
24576                Some(pane.downgrade()),
24577                true,
24578                window,
24579                cx,
24580            )
24581        })
24582        .unwrap()
24583        .await
24584        .downcast::<Editor>()
24585        .unwrap();
24586    pane.update(cx, |pane, cx| {
24587        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24588        open_editor.update(cx, |editor, cx| {
24589            assert_eq!(
24590                editor.display_text(cx),
24591                main_text,
24592                "No folds: even after enabling the restoration, previous editor's data should not be saved to be used for the restoration"
24593            );
24594        })
24595    });
24596}
24597
24598#[gpui::test]
24599async fn test_hide_mouse_context_menu_on_modal_opened(cx: &mut TestAppContext) {
24600    struct EmptyModalView {
24601        focus_handle: gpui::FocusHandle,
24602    }
24603    impl EventEmitter<DismissEvent> for EmptyModalView {}
24604    impl Render for EmptyModalView {
24605        fn render(&mut self, _: &mut Window, _: &mut Context<'_, Self>) -> impl IntoElement {
24606            div()
24607        }
24608    }
24609    impl Focusable for EmptyModalView {
24610        fn focus_handle(&self, _cx: &App) -> gpui::FocusHandle {
24611            self.focus_handle.clone()
24612        }
24613    }
24614    impl workspace::ModalView for EmptyModalView {}
24615    fn new_empty_modal_view(cx: &App) -> EmptyModalView {
24616        EmptyModalView {
24617            focus_handle: cx.focus_handle(),
24618        }
24619    }
24620
24621    init_test(cx, |_| {});
24622
24623    let fs = FakeFs::new(cx.executor());
24624    let project = Project::test(fs, [], cx).await;
24625    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24626    let buffer = cx.update(|cx| MultiBuffer::build_simple("hello world!", cx));
24627    let cx = &mut VisualTestContext::from_window(*workspace.deref(), cx);
24628    let editor = cx.new_window_entity(|window, cx| {
24629        Editor::new(
24630            EditorMode::full(),
24631            buffer,
24632            Some(project.clone()),
24633            window,
24634            cx,
24635        )
24636    });
24637    workspace
24638        .update(cx, |workspace, window, cx| {
24639            workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
24640        })
24641        .unwrap();
24642    editor.update_in(cx, |editor, window, cx| {
24643        editor.open_context_menu(&OpenContextMenu, window, cx);
24644        assert!(editor.mouse_context_menu.is_some());
24645    });
24646    workspace
24647        .update(cx, |workspace, window, cx| {
24648            workspace.toggle_modal(window, cx, |_, cx| new_empty_modal_view(cx));
24649        })
24650        .unwrap();
24651    cx.read(|cx| {
24652        assert!(editor.read(cx).mouse_context_menu.is_none());
24653    });
24654}
24655
24656fn set_linked_edit_ranges(
24657    opening: (Point, Point),
24658    closing: (Point, Point),
24659    editor: &mut Editor,
24660    cx: &mut Context<Editor>,
24661) {
24662    let Some((buffer, _)) = editor
24663        .buffer
24664        .read(cx)
24665        .text_anchor_for_position(editor.selections.newest_anchor().start, cx)
24666    else {
24667        panic!("Failed to get buffer for selection position");
24668    };
24669    let buffer = buffer.read(cx);
24670    let buffer_id = buffer.remote_id();
24671    let opening_range = buffer.anchor_before(opening.0)..buffer.anchor_after(opening.1);
24672    let closing_range = buffer.anchor_before(closing.0)..buffer.anchor_after(closing.1);
24673    let mut linked_ranges = HashMap::default();
24674    linked_ranges.insert(buffer_id, vec![(opening_range, vec![closing_range])]);
24675    editor.linked_edit_ranges = LinkedEditingRanges(linked_ranges);
24676}
24677
24678#[gpui::test]
24679async fn test_html_linked_edits_on_completion(cx: &mut TestAppContext) {
24680    init_test(cx, |_| {});
24681
24682    let fs = FakeFs::new(cx.executor());
24683    fs.insert_file(path!("/file.html"), Default::default())
24684        .await;
24685
24686    let project = Project::test(fs, [path!("/").as_ref()], cx).await;
24687
24688    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24689    let html_language = Arc::new(Language::new(
24690        LanguageConfig {
24691            name: "HTML".into(),
24692            matcher: LanguageMatcher {
24693                path_suffixes: vec!["html".to_string()],
24694                ..LanguageMatcher::default()
24695            },
24696            brackets: BracketPairConfig {
24697                pairs: vec![BracketPair {
24698                    start: "<".into(),
24699                    end: ">".into(),
24700                    close: true,
24701                    ..Default::default()
24702                }],
24703                ..Default::default()
24704            },
24705            ..Default::default()
24706        },
24707        Some(tree_sitter_html::LANGUAGE.into()),
24708    ));
24709    language_registry.add(html_language);
24710    let mut fake_servers = language_registry.register_fake_lsp(
24711        "HTML",
24712        FakeLspAdapter {
24713            capabilities: lsp::ServerCapabilities {
24714                completion_provider: Some(lsp::CompletionOptions {
24715                    resolve_provider: Some(true),
24716                    ..Default::default()
24717                }),
24718                ..Default::default()
24719            },
24720            ..Default::default()
24721        },
24722    );
24723
24724    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
24725    let cx = &mut VisualTestContext::from_window(*workspace, cx);
24726
24727    let worktree_id = workspace
24728        .update(cx, |workspace, _window, cx| {
24729            workspace.project().update(cx, |project, cx| {
24730                project.worktrees(cx).next().unwrap().read(cx).id()
24731            })
24732        })
24733        .unwrap();
24734    project
24735        .update(cx, |project, cx| {
24736            project.open_local_buffer_with_lsp(path!("/file.html"), cx)
24737        })
24738        .await
24739        .unwrap();
24740    let editor = workspace
24741        .update(cx, |workspace, window, cx| {
24742            workspace.open_path((worktree_id, rel_path("file.html")), None, true, window, cx)
24743        })
24744        .unwrap()
24745        .await
24746        .unwrap()
24747        .downcast::<Editor>()
24748        .unwrap();
24749
24750    let fake_server = fake_servers.next().await.unwrap();
24751    editor.update_in(cx, |editor, window, cx| {
24752        editor.set_text("<ad></ad>", window, cx);
24753        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24754            selections.select_ranges([Point::new(0, 3)..Point::new(0, 3)]);
24755        });
24756        set_linked_edit_ranges(
24757            (Point::new(0, 1), Point::new(0, 3)),
24758            (Point::new(0, 6), Point::new(0, 8)),
24759            editor,
24760            cx,
24761        );
24762    });
24763    let mut completion_handle =
24764        fake_server.set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
24765            Ok(Some(lsp::CompletionResponse::Array(vec![
24766                lsp::CompletionItem {
24767                    label: "head".to_string(),
24768                    text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24769                        lsp::InsertReplaceEdit {
24770                            new_text: "head".to_string(),
24771                            insert: lsp::Range::new(
24772                                lsp::Position::new(0, 1),
24773                                lsp::Position::new(0, 3),
24774                            ),
24775                            replace: lsp::Range::new(
24776                                lsp::Position::new(0, 1),
24777                                lsp::Position::new(0, 3),
24778                            ),
24779                        },
24780                    )),
24781                    ..Default::default()
24782                },
24783            ])))
24784        });
24785    editor.update_in(cx, |editor, window, cx| {
24786        editor.show_completions(&ShowCompletions, window, cx);
24787    });
24788    cx.run_until_parked();
24789    completion_handle.next().await.unwrap();
24790    editor.update(cx, |editor, _| {
24791        assert!(
24792            editor.context_menu_visible(),
24793            "Completion menu should be visible"
24794        );
24795    });
24796    editor.update_in(cx, |editor, window, cx| {
24797        editor.confirm_completion(&ConfirmCompletion::default(), window, cx)
24798    });
24799    cx.executor().run_until_parked();
24800    editor.update(cx, |editor, cx| {
24801        assert_eq!(editor.text(cx), "<head></head>");
24802    });
24803}
24804
24805#[gpui::test]
24806async fn test_linked_edits_on_typing_punctuation(cx: &mut TestAppContext) {
24807    init_test(cx, |_| {});
24808
24809    let mut cx = EditorTestContext::new(cx).await;
24810    let language = Arc::new(Language::new(
24811        LanguageConfig {
24812            name: "TSX".into(),
24813            matcher: LanguageMatcher {
24814                path_suffixes: vec!["tsx".to_string()],
24815                ..LanguageMatcher::default()
24816            },
24817            brackets: BracketPairConfig {
24818                pairs: vec![BracketPair {
24819                    start: "<".into(),
24820                    end: ">".into(),
24821                    close: true,
24822                    ..Default::default()
24823                }],
24824                ..Default::default()
24825            },
24826            linked_edit_characters: HashSet::from_iter(['.']),
24827            ..Default::default()
24828        },
24829        Some(tree_sitter_typescript::LANGUAGE_TSX.into()),
24830    ));
24831    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
24832
24833    // Test typing > does not extend linked pair
24834    cx.set_state("<divˇ<div></div>");
24835    cx.update_editor(|editor, _, cx| {
24836        set_linked_edit_ranges(
24837            (Point::new(0, 1), Point::new(0, 4)),
24838            (Point::new(0, 11), Point::new(0, 14)),
24839            editor,
24840            cx,
24841        );
24842    });
24843    cx.update_editor(|editor, window, cx| {
24844        editor.handle_input(">", window, cx);
24845    });
24846    cx.assert_editor_state("<div>ˇ<div></div>");
24847
24848    // Test typing . do extend linked pair
24849    cx.set_state("<Animatedˇ></Animated>");
24850    cx.update_editor(|editor, _, cx| {
24851        set_linked_edit_ranges(
24852            (Point::new(0, 1), Point::new(0, 9)),
24853            (Point::new(0, 12), Point::new(0, 20)),
24854            editor,
24855            cx,
24856        );
24857    });
24858    cx.update_editor(|editor, window, cx| {
24859        editor.handle_input(".", window, cx);
24860    });
24861    cx.assert_editor_state("<Animated.ˇ></Animated.>");
24862    cx.update_editor(|editor, _, cx| {
24863        set_linked_edit_ranges(
24864            (Point::new(0, 1), Point::new(0, 10)),
24865            (Point::new(0, 13), Point::new(0, 21)),
24866            editor,
24867            cx,
24868        );
24869    });
24870    cx.update_editor(|editor, window, cx| {
24871        editor.handle_input("V", window, cx);
24872    });
24873    cx.assert_editor_state("<Animated.Vˇ></Animated.V>");
24874}
24875
24876#[gpui::test]
24877async fn test_invisible_worktree_servers(cx: &mut TestAppContext) {
24878    init_test(cx, |_| {});
24879
24880    let fs = FakeFs::new(cx.executor());
24881    fs.insert_tree(
24882        path!("/root"),
24883        json!({
24884            "a": {
24885                "main.rs": "fn main() {}",
24886            },
24887            "foo": {
24888                "bar": {
24889                    "external_file.rs": "pub mod external {}",
24890                }
24891            }
24892        }),
24893    )
24894    .await;
24895
24896    let project = Project::test(fs, [path!("/root/a").as_ref()], cx).await;
24897    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
24898    language_registry.add(rust_lang());
24899    let _fake_servers = language_registry.register_fake_lsp(
24900        "Rust",
24901        FakeLspAdapter {
24902            ..FakeLspAdapter::default()
24903        },
24904    );
24905    let (workspace, cx) =
24906        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
24907    let worktree_id = workspace.update(cx, |workspace, cx| {
24908        workspace.project().update(cx, |project, cx| {
24909            project.worktrees(cx).next().unwrap().read(cx).id()
24910        })
24911    });
24912
24913    let assert_language_servers_count =
24914        |expected: usize, context: &str, cx: &mut VisualTestContext| {
24915            project.update(cx, |project, cx| {
24916                let current = project
24917                    .lsp_store()
24918                    .read(cx)
24919                    .as_local()
24920                    .unwrap()
24921                    .language_servers
24922                    .len();
24923                assert_eq!(expected, current, "{context}");
24924            });
24925        };
24926
24927    assert_language_servers_count(
24928        0,
24929        "No servers should be running before any file is open",
24930        cx,
24931    );
24932    let pane = workspace.update(cx, |workspace, _| workspace.active_pane().clone());
24933    let main_editor = workspace
24934        .update_in(cx, |workspace, window, cx| {
24935            workspace.open_path(
24936                (worktree_id, rel_path("main.rs")),
24937                Some(pane.downgrade()),
24938                true,
24939                window,
24940                cx,
24941            )
24942        })
24943        .unwrap()
24944        .await
24945        .downcast::<Editor>()
24946        .unwrap();
24947    pane.update(cx, |pane, cx| {
24948        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24949        open_editor.update(cx, |editor, cx| {
24950            assert_eq!(
24951                editor.display_text(cx),
24952                "fn main() {}",
24953                "Original main.rs text on initial open",
24954            );
24955        });
24956        assert_eq!(open_editor, main_editor);
24957    });
24958    assert_language_servers_count(1, "First *.rs file starts a language server", cx);
24959
24960    let external_editor = workspace
24961        .update_in(cx, |workspace, window, cx| {
24962            workspace.open_abs_path(
24963                PathBuf::from("/root/foo/bar/external_file.rs"),
24964                OpenOptions::default(),
24965                window,
24966                cx,
24967            )
24968        })
24969        .await
24970        .expect("opening external file")
24971        .downcast::<Editor>()
24972        .expect("downcasted external file's open element to editor");
24973    pane.update(cx, |pane, cx| {
24974        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
24975        open_editor.update(cx, |editor, cx| {
24976            assert_eq!(
24977                editor.display_text(cx),
24978                "pub mod external {}",
24979                "External file is open now",
24980            );
24981        });
24982        assert_eq!(open_editor, external_editor);
24983    });
24984    assert_language_servers_count(
24985        1,
24986        "Second, external, *.rs file should join the existing server",
24987        cx,
24988    );
24989
24990    pane.update_in(cx, |pane, window, cx| {
24991        pane.close_active_item(&CloseActiveItem::default(), window, cx)
24992    })
24993    .await
24994    .unwrap();
24995    pane.update_in(cx, |pane, window, cx| {
24996        pane.navigate_backward(&Default::default(), window, cx);
24997    });
24998    cx.run_until_parked();
24999    pane.update(cx, |pane, cx| {
25000        let open_editor = pane.active_item().unwrap().downcast::<Editor>().unwrap();
25001        open_editor.update(cx, |editor, cx| {
25002            assert_eq!(
25003                editor.display_text(cx),
25004                "pub mod external {}",
25005                "External file is open now",
25006            );
25007        });
25008    });
25009    assert_language_servers_count(
25010        1,
25011        "After closing and reopening (with navigate back) of an external file, no extra language servers should appear",
25012        cx,
25013    );
25014
25015    cx.update(|_, cx| {
25016        workspace::reload(cx);
25017    });
25018    assert_language_servers_count(
25019        1,
25020        "After reloading the worktree with local and external files opened, only one project should be started",
25021        cx,
25022    );
25023}
25024
25025#[gpui::test]
25026async fn test_tab_in_leading_whitespace_auto_indents_for_python(cx: &mut TestAppContext) {
25027    init_test(cx, |_| {});
25028
25029    let mut cx = EditorTestContext::new(cx).await;
25030    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25031    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
25032
25033    // test cursor move to start of each line on tab
25034    // for `if`, `elif`, `else`, `while`, `with` and `for`
25035    cx.set_state(indoc! {"
25036        def main():
25037        ˇ    for item in items:
25038        ˇ        while item.active:
25039        ˇ            if item.value > 10:
25040        ˇ                continue
25041        ˇ            elif item.value < 0:
25042        ˇ                break
25043        ˇ            else:
25044        ˇ                with item.context() as ctx:
25045        ˇ                    yield count
25046        ˇ        else:
25047        ˇ            log('while else')
25048        ˇ    else:
25049        ˇ        log('for else')
25050    "});
25051    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25052    cx.assert_editor_state(indoc! {"
25053        def main():
25054            ˇfor item in items:
25055                ˇwhile item.active:
25056                    ˇif item.value > 10:
25057                        ˇcontinue
25058                    ˇelif item.value < 0:
25059                        ˇbreak
25060                    ˇelse:
25061                        ˇwith item.context() as ctx:
25062                            ˇyield count
25063                ˇelse:
25064                    ˇlog('while else')
25065            ˇelse:
25066                ˇlog('for else')
25067    "});
25068    // test relative indent is preserved when tab
25069    // for `if`, `elif`, `else`, `while`, `with` and `for`
25070    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25071    cx.assert_editor_state(indoc! {"
25072        def main():
25073                ˇfor item in items:
25074                    ˇwhile item.active:
25075                        ˇif item.value > 10:
25076                            ˇcontinue
25077                        ˇelif item.value < 0:
25078                            ˇbreak
25079                        ˇelse:
25080                            ˇwith item.context() as ctx:
25081                                ˇyield count
25082                    ˇelse:
25083                        ˇlog('while else')
25084                ˇelse:
25085                    ˇlog('for else')
25086    "});
25087
25088    // test cursor move to start of each line on tab
25089    // for `try`, `except`, `else`, `finally`, `match` and `def`
25090    cx.set_state(indoc! {"
25091        def main():
25092        ˇ    try:
25093        ˇ        fetch()
25094        ˇ    except ValueError:
25095        ˇ        handle_error()
25096        ˇ    else:
25097        ˇ        match value:
25098        ˇ            case _:
25099        ˇ    finally:
25100        ˇ        def status():
25101        ˇ            return 0
25102    "});
25103    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25104    cx.assert_editor_state(indoc! {"
25105        def main():
25106            ˇtry:
25107                ˇfetch()
25108            ˇexcept ValueError:
25109                ˇhandle_error()
25110            ˇelse:
25111                ˇmatch value:
25112                    ˇcase _:
25113            ˇfinally:
25114                ˇdef status():
25115                    ˇreturn 0
25116    "});
25117    // test relative indent is preserved when tab
25118    // for `try`, `except`, `else`, `finally`, `match` and `def`
25119    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25120    cx.assert_editor_state(indoc! {"
25121        def main():
25122                ˇtry:
25123                    ˇfetch()
25124                ˇexcept ValueError:
25125                    ˇhandle_error()
25126                ˇelse:
25127                    ˇmatch value:
25128                        ˇcase _:
25129                ˇfinally:
25130                    ˇdef status():
25131                        ˇreturn 0
25132    "});
25133}
25134
25135#[gpui::test]
25136async fn test_outdent_after_input_for_python(cx: &mut TestAppContext) {
25137    init_test(cx, |_| {});
25138
25139    let mut cx = EditorTestContext::new(cx).await;
25140    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25141    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
25142
25143    // test `else` auto outdents when typed inside `if` block
25144    cx.set_state(indoc! {"
25145        def main():
25146            if i == 2:
25147                return
25148                ˇ
25149    "});
25150    cx.update_editor(|editor, window, cx| {
25151        editor.handle_input("else:", window, cx);
25152    });
25153    cx.assert_editor_state(indoc! {"
25154        def main():
25155            if i == 2:
25156                return
25157            else:ˇ
25158    "});
25159
25160    // test `except` auto outdents when typed inside `try` block
25161    cx.set_state(indoc! {"
25162        def main():
25163            try:
25164                i = 2
25165                ˇ
25166    "});
25167    cx.update_editor(|editor, window, cx| {
25168        editor.handle_input("except:", window, cx);
25169    });
25170    cx.assert_editor_state(indoc! {"
25171        def main():
25172            try:
25173                i = 2
25174            except:ˇ
25175    "});
25176
25177    // test `else` auto outdents when typed inside `except` block
25178    cx.set_state(indoc! {"
25179        def main():
25180            try:
25181                i = 2
25182            except:
25183                j = 2
25184                ˇ
25185    "});
25186    cx.update_editor(|editor, window, cx| {
25187        editor.handle_input("else:", window, cx);
25188    });
25189    cx.assert_editor_state(indoc! {"
25190        def main():
25191            try:
25192                i = 2
25193            except:
25194                j = 2
25195            else:ˇ
25196    "});
25197
25198    // test `finally` auto outdents when typed inside `else` block
25199    cx.set_state(indoc! {"
25200        def main():
25201            try:
25202                i = 2
25203            except:
25204                j = 2
25205            else:
25206                k = 2
25207                ˇ
25208    "});
25209    cx.update_editor(|editor, window, cx| {
25210        editor.handle_input("finally:", window, cx);
25211    });
25212    cx.assert_editor_state(indoc! {"
25213        def main():
25214            try:
25215                i = 2
25216            except:
25217                j = 2
25218            else:
25219                k = 2
25220            finally:ˇ
25221    "});
25222
25223    // test `else` does not outdents when typed inside `except` block right after for block
25224    cx.set_state(indoc! {"
25225        def main():
25226            try:
25227                i = 2
25228            except:
25229                for i in range(n):
25230                    pass
25231                ˇ
25232    "});
25233    cx.update_editor(|editor, window, cx| {
25234        editor.handle_input("else:", window, cx);
25235    });
25236    cx.assert_editor_state(indoc! {"
25237        def main():
25238            try:
25239                i = 2
25240            except:
25241                for i in range(n):
25242                    pass
25243                else:ˇ
25244    "});
25245
25246    // test `finally` auto outdents when typed inside `else` block right after for block
25247    cx.set_state(indoc! {"
25248        def main():
25249            try:
25250                i = 2
25251            except:
25252                j = 2
25253            else:
25254                for i in range(n):
25255                    pass
25256                ˇ
25257    "});
25258    cx.update_editor(|editor, window, cx| {
25259        editor.handle_input("finally:", window, cx);
25260    });
25261    cx.assert_editor_state(indoc! {"
25262        def main():
25263            try:
25264                i = 2
25265            except:
25266                j = 2
25267            else:
25268                for i in range(n):
25269                    pass
25270            finally:ˇ
25271    "});
25272
25273    // test `except` outdents to inner "try" block
25274    cx.set_state(indoc! {"
25275        def main():
25276            try:
25277                i = 2
25278                if i == 2:
25279                    try:
25280                        i = 3
25281                        ˇ
25282    "});
25283    cx.update_editor(|editor, window, cx| {
25284        editor.handle_input("except:", window, cx);
25285    });
25286    cx.assert_editor_state(indoc! {"
25287        def main():
25288            try:
25289                i = 2
25290                if i == 2:
25291                    try:
25292                        i = 3
25293                    except:ˇ
25294    "});
25295
25296    // test `except` outdents to outer "try" block
25297    cx.set_state(indoc! {"
25298        def main():
25299            try:
25300                i = 2
25301                if i == 2:
25302                    try:
25303                        i = 3
25304                ˇ
25305    "});
25306    cx.update_editor(|editor, window, cx| {
25307        editor.handle_input("except:", window, cx);
25308    });
25309    cx.assert_editor_state(indoc! {"
25310        def main():
25311            try:
25312                i = 2
25313                if i == 2:
25314                    try:
25315                        i = 3
25316            except:ˇ
25317    "});
25318
25319    // test `else` stays at correct indent when typed after `for` block
25320    cx.set_state(indoc! {"
25321        def main():
25322            for i in range(10):
25323                if i == 3:
25324                    break
25325            ˇ
25326    "});
25327    cx.update_editor(|editor, window, cx| {
25328        editor.handle_input("else:", window, cx);
25329    });
25330    cx.assert_editor_state(indoc! {"
25331        def main():
25332            for i in range(10):
25333                if i == 3:
25334                    break
25335            else:ˇ
25336    "});
25337
25338    // test does not outdent on typing after line with square brackets
25339    cx.set_state(indoc! {"
25340        def f() -> list[str]:
25341            ˇ
25342    "});
25343    cx.update_editor(|editor, window, cx| {
25344        editor.handle_input("a", window, cx);
25345    });
25346    cx.assert_editor_state(indoc! {"
25347        def f() -> list[str]:
2534825349    "});
25350
25351    // test does not outdent on typing : after case keyword
25352    cx.set_state(indoc! {"
25353        match 1:
25354            caseˇ
25355    "});
25356    cx.update_editor(|editor, window, cx| {
25357        editor.handle_input(":", window, cx);
25358    });
25359    cx.assert_editor_state(indoc! {"
25360        match 1:
25361            case:ˇ
25362    "});
25363}
25364
25365#[gpui::test]
25366async fn test_indent_on_newline_for_python(cx: &mut TestAppContext) {
25367    init_test(cx, |_| {});
25368    update_test_language_settings(cx, |settings| {
25369        settings.defaults.extend_comment_on_newline = Some(false);
25370    });
25371    let mut cx = EditorTestContext::new(cx).await;
25372    let language = languages::language("python", tree_sitter_python::LANGUAGE.into());
25373    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
25374
25375    // test correct indent after newline on comment
25376    cx.set_state(indoc! {"
25377        # COMMENT:ˇ
25378    "});
25379    cx.update_editor(|editor, window, cx| {
25380        editor.newline(&Newline, window, cx);
25381    });
25382    cx.assert_editor_state(indoc! {"
25383        # COMMENT:
25384        ˇ
25385    "});
25386
25387    // test correct indent after newline in brackets
25388    cx.set_state(indoc! {"
25389        {ˇ}
25390    "});
25391    cx.update_editor(|editor, window, cx| {
25392        editor.newline(&Newline, window, cx);
25393    });
25394    cx.run_until_parked();
25395    cx.assert_editor_state(indoc! {"
25396        {
25397            ˇ
25398        }
25399    "});
25400
25401    cx.set_state(indoc! {"
25402        (ˇ)
25403    "});
25404    cx.update_editor(|editor, window, cx| {
25405        editor.newline(&Newline, window, cx);
25406    });
25407    cx.run_until_parked();
25408    cx.assert_editor_state(indoc! {"
25409        (
25410            ˇ
25411        )
25412    "});
25413
25414    // do not indent after empty lists or dictionaries
25415    cx.set_state(indoc! {"
25416        a = []ˇ
25417    "});
25418    cx.update_editor(|editor, window, cx| {
25419        editor.newline(&Newline, window, cx);
25420    });
25421    cx.run_until_parked();
25422    cx.assert_editor_state(indoc! {"
25423        a = []
25424        ˇ
25425    "});
25426}
25427
25428#[gpui::test]
25429async fn test_tab_in_leading_whitespace_auto_indents_for_bash(cx: &mut TestAppContext) {
25430    init_test(cx, |_| {});
25431
25432    let mut cx = EditorTestContext::new(cx).await;
25433    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25434    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
25435
25436    // test cursor move to start of each line on tab
25437    // for `if`, `elif`, `else`, `while`, `for`, `case` and `function`
25438    cx.set_state(indoc! {"
25439        function main() {
25440        ˇ    for item in $items; do
25441        ˇ        while [ -n \"$item\" ]; do
25442        ˇ            if [ \"$value\" -gt 10 ]; then
25443        ˇ                continue
25444        ˇ            elif [ \"$value\" -lt 0 ]; then
25445        ˇ                break
25446        ˇ            else
25447        ˇ                echo \"$item\"
25448        ˇ            fi
25449        ˇ        done
25450        ˇ    done
25451        ˇ}
25452    "});
25453    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25454    cx.assert_editor_state(indoc! {"
25455        function main() {
25456            ˇfor item in $items; do
25457                ˇwhile [ -n \"$item\" ]; do
25458                    ˇif [ \"$value\" -gt 10 ]; then
25459                        ˇcontinue
25460                    ˇelif [ \"$value\" -lt 0 ]; then
25461                        ˇbreak
25462                    ˇelse
25463                        ˇecho \"$item\"
25464                    ˇfi
25465                ˇdone
25466            ˇdone
25467        ˇ}
25468    "});
25469    // test relative indent is preserved when tab
25470    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25471    cx.assert_editor_state(indoc! {"
25472        function main() {
25473                ˇfor item in $items; do
25474                    ˇwhile [ -n \"$item\" ]; do
25475                        ˇif [ \"$value\" -gt 10 ]; then
25476                            ˇcontinue
25477                        ˇelif [ \"$value\" -lt 0 ]; then
25478                            ˇbreak
25479                        ˇelse
25480                            ˇecho \"$item\"
25481                        ˇfi
25482                    ˇdone
25483                ˇdone
25484            ˇ}
25485    "});
25486
25487    // test cursor move to start of each line on tab
25488    // for `case` statement with patterns
25489    cx.set_state(indoc! {"
25490        function handle() {
25491        ˇ    case \"$1\" in
25492        ˇ        start)
25493        ˇ            echo \"a\"
25494        ˇ            ;;
25495        ˇ        stop)
25496        ˇ            echo \"b\"
25497        ˇ            ;;
25498        ˇ        *)
25499        ˇ            echo \"c\"
25500        ˇ            ;;
25501        ˇ    esac
25502        ˇ}
25503    "});
25504    cx.update_editor(|e, window, cx| e.tab(&Tab, window, cx));
25505    cx.assert_editor_state(indoc! {"
25506        function handle() {
25507            ˇcase \"$1\" in
25508                ˇstart)
25509                    ˇecho \"a\"
25510                    ˇ;;
25511                ˇstop)
25512                    ˇecho \"b\"
25513                    ˇ;;
25514                ˇ*)
25515                    ˇecho \"c\"
25516                    ˇ;;
25517            ˇesac
25518        ˇ}
25519    "});
25520}
25521
25522#[gpui::test]
25523async fn test_indent_after_input_for_bash(cx: &mut TestAppContext) {
25524    init_test(cx, |_| {});
25525
25526    let mut cx = EditorTestContext::new(cx).await;
25527    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25528    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
25529
25530    // test indents on comment insert
25531    cx.set_state(indoc! {"
25532        function main() {
25533        ˇ    for item in $items; do
25534        ˇ        while [ -n \"$item\" ]; do
25535        ˇ            if [ \"$value\" -gt 10 ]; then
25536        ˇ                continue
25537        ˇ            elif [ \"$value\" -lt 0 ]; then
25538        ˇ                break
25539        ˇ            else
25540        ˇ                echo \"$item\"
25541        ˇ            fi
25542        ˇ        done
25543        ˇ    done
25544        ˇ}
25545    "});
25546    cx.update_editor(|e, window, cx| e.handle_input("#", window, cx));
25547    cx.assert_editor_state(indoc! {"
25548        function main() {
25549        #ˇ    for item in $items; do
25550        #ˇ        while [ -n \"$item\" ]; do
25551        #ˇ            if [ \"$value\" -gt 10 ]; then
25552        #ˇ                continue
25553        #ˇ            elif [ \"$value\" -lt 0 ]; then
25554        #ˇ                break
25555        #ˇ            else
25556        #ˇ                echo \"$item\"
25557        #ˇ            fi
25558        #ˇ        done
25559        #ˇ    done
25560        #ˇ}
25561    "});
25562}
25563
25564#[gpui::test]
25565async fn test_outdent_after_input_for_bash(cx: &mut TestAppContext) {
25566    init_test(cx, |_| {});
25567
25568    let mut cx = EditorTestContext::new(cx).await;
25569    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25570    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
25571
25572    // test `else` auto outdents when typed inside `if` block
25573    cx.set_state(indoc! {"
25574        if [ \"$1\" = \"test\" ]; then
25575            echo \"foo bar\"
25576            ˇ
25577    "});
25578    cx.update_editor(|editor, window, cx| {
25579        editor.handle_input("else", window, cx);
25580    });
25581    cx.assert_editor_state(indoc! {"
25582        if [ \"$1\" = \"test\" ]; then
25583            echo \"foo bar\"
25584        elseˇ
25585    "});
25586
25587    // test `elif` auto outdents when typed inside `if` block
25588    cx.set_state(indoc! {"
25589        if [ \"$1\" = \"test\" ]; then
25590            echo \"foo bar\"
25591            ˇ
25592    "});
25593    cx.update_editor(|editor, window, cx| {
25594        editor.handle_input("elif", window, cx);
25595    });
25596    cx.assert_editor_state(indoc! {"
25597        if [ \"$1\" = \"test\" ]; then
25598            echo \"foo bar\"
25599        elifˇ
25600    "});
25601
25602    // test `fi` auto outdents when typed inside `else` block
25603    cx.set_state(indoc! {"
25604        if [ \"$1\" = \"test\" ]; then
25605            echo \"foo bar\"
25606        else
25607            echo \"bar baz\"
25608            ˇ
25609    "});
25610    cx.update_editor(|editor, window, cx| {
25611        editor.handle_input("fi", window, cx);
25612    });
25613    cx.assert_editor_state(indoc! {"
25614        if [ \"$1\" = \"test\" ]; then
25615            echo \"foo bar\"
25616        else
25617            echo \"bar baz\"
25618        fiˇ
25619    "});
25620
25621    // test `done` auto outdents when typed inside `while` block
25622    cx.set_state(indoc! {"
25623        while read line; do
25624            echo \"$line\"
25625            ˇ
25626    "});
25627    cx.update_editor(|editor, window, cx| {
25628        editor.handle_input("done", window, cx);
25629    });
25630    cx.assert_editor_state(indoc! {"
25631        while read line; do
25632            echo \"$line\"
25633        doneˇ
25634    "});
25635
25636    // test `done` auto outdents when typed inside `for` block
25637    cx.set_state(indoc! {"
25638        for file in *.txt; do
25639            cat \"$file\"
25640            ˇ
25641    "});
25642    cx.update_editor(|editor, window, cx| {
25643        editor.handle_input("done", window, cx);
25644    });
25645    cx.assert_editor_state(indoc! {"
25646        for file in *.txt; do
25647            cat \"$file\"
25648        doneˇ
25649    "});
25650
25651    // test `esac` auto outdents when typed inside `case` block
25652    cx.set_state(indoc! {"
25653        case \"$1\" in
25654            start)
25655                echo \"foo bar\"
25656                ;;
25657            stop)
25658                echo \"bar baz\"
25659                ;;
25660            ˇ
25661    "});
25662    cx.update_editor(|editor, window, cx| {
25663        editor.handle_input("esac", window, cx);
25664    });
25665    cx.assert_editor_state(indoc! {"
25666        case \"$1\" in
25667            start)
25668                echo \"foo bar\"
25669                ;;
25670            stop)
25671                echo \"bar baz\"
25672                ;;
25673        esacˇ
25674    "});
25675
25676    // test `*)` auto outdents when typed inside `case` block
25677    cx.set_state(indoc! {"
25678        case \"$1\" in
25679            start)
25680                echo \"foo bar\"
25681                ;;
25682                ˇ
25683    "});
25684    cx.update_editor(|editor, window, cx| {
25685        editor.handle_input("*)", window, cx);
25686    });
25687    cx.assert_editor_state(indoc! {"
25688        case \"$1\" in
25689            start)
25690                echo \"foo bar\"
25691                ;;
25692            *)ˇ
25693    "});
25694
25695    // test `fi` outdents to correct level with nested if blocks
25696    cx.set_state(indoc! {"
25697        if [ \"$1\" = \"test\" ]; then
25698            echo \"outer if\"
25699            if [ \"$2\" = \"debug\" ]; then
25700                echo \"inner if\"
25701                ˇ
25702    "});
25703    cx.update_editor(|editor, window, cx| {
25704        editor.handle_input("fi", window, cx);
25705    });
25706    cx.assert_editor_state(indoc! {"
25707        if [ \"$1\" = \"test\" ]; then
25708            echo \"outer if\"
25709            if [ \"$2\" = \"debug\" ]; then
25710                echo \"inner if\"
25711            fiˇ
25712    "});
25713}
25714
25715#[gpui::test]
25716async fn test_indent_on_newline_for_bash(cx: &mut TestAppContext) {
25717    init_test(cx, |_| {});
25718    update_test_language_settings(cx, |settings| {
25719        settings.defaults.extend_comment_on_newline = Some(false);
25720    });
25721    let mut cx = EditorTestContext::new(cx).await;
25722    let language = languages::language("bash", tree_sitter_bash::LANGUAGE.into());
25723    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(language), cx));
25724
25725    // test correct indent after newline on comment
25726    cx.set_state(indoc! {"
25727        # COMMENT:ˇ
25728    "});
25729    cx.update_editor(|editor, window, cx| {
25730        editor.newline(&Newline, window, cx);
25731    });
25732    cx.assert_editor_state(indoc! {"
25733        # COMMENT:
25734        ˇ
25735    "});
25736
25737    // test correct indent after newline after `then`
25738    cx.set_state(indoc! {"
25739
25740        if [ \"$1\" = \"test\" ]; thenˇ
25741    "});
25742    cx.update_editor(|editor, window, cx| {
25743        editor.newline(&Newline, window, cx);
25744    });
25745    cx.run_until_parked();
25746    cx.assert_editor_state(indoc! {"
25747
25748        if [ \"$1\" = \"test\" ]; then
25749            ˇ
25750    "});
25751
25752    // test correct indent after newline after `else`
25753    cx.set_state(indoc! {"
25754        if [ \"$1\" = \"test\" ]; then
25755        elseˇ
25756    "});
25757    cx.update_editor(|editor, window, cx| {
25758        editor.newline(&Newline, window, cx);
25759    });
25760    cx.run_until_parked();
25761    cx.assert_editor_state(indoc! {"
25762        if [ \"$1\" = \"test\" ]; then
25763        else
25764            ˇ
25765    "});
25766
25767    // test correct indent after newline after `elif`
25768    cx.set_state(indoc! {"
25769        if [ \"$1\" = \"test\" ]; then
25770        elifˇ
25771    "});
25772    cx.update_editor(|editor, window, cx| {
25773        editor.newline(&Newline, window, cx);
25774    });
25775    cx.run_until_parked();
25776    cx.assert_editor_state(indoc! {"
25777        if [ \"$1\" = \"test\" ]; then
25778        elif
25779            ˇ
25780    "});
25781
25782    // test correct indent after newline after `do`
25783    cx.set_state(indoc! {"
25784        for file in *.txt; doˇ
25785    "});
25786    cx.update_editor(|editor, window, cx| {
25787        editor.newline(&Newline, window, cx);
25788    });
25789    cx.run_until_parked();
25790    cx.assert_editor_state(indoc! {"
25791        for file in *.txt; do
25792            ˇ
25793    "});
25794
25795    // test correct indent after newline after case pattern
25796    cx.set_state(indoc! {"
25797        case \"$1\" in
25798            start)ˇ
25799    "});
25800    cx.update_editor(|editor, window, cx| {
25801        editor.newline(&Newline, window, cx);
25802    });
25803    cx.run_until_parked();
25804    cx.assert_editor_state(indoc! {"
25805        case \"$1\" in
25806            start)
25807                ˇ
25808    "});
25809
25810    // test correct indent after newline after case pattern
25811    cx.set_state(indoc! {"
25812        case \"$1\" in
25813            start)
25814                ;;
25815            *)ˇ
25816    "});
25817    cx.update_editor(|editor, window, cx| {
25818        editor.newline(&Newline, window, cx);
25819    });
25820    cx.run_until_parked();
25821    cx.assert_editor_state(indoc! {"
25822        case \"$1\" in
25823            start)
25824                ;;
25825            *)
25826                ˇ
25827    "});
25828
25829    // test correct indent after newline after function opening brace
25830    cx.set_state(indoc! {"
25831        function test() {ˇ}
25832    "});
25833    cx.update_editor(|editor, window, cx| {
25834        editor.newline(&Newline, window, cx);
25835    });
25836    cx.run_until_parked();
25837    cx.assert_editor_state(indoc! {"
25838        function test() {
25839            ˇ
25840        }
25841    "});
25842
25843    // test no extra indent after semicolon on same line
25844    cx.set_state(indoc! {"
25845        echo \"test\"25846    "});
25847    cx.update_editor(|editor, window, cx| {
25848        editor.newline(&Newline, window, cx);
25849    });
25850    cx.run_until_parked();
25851    cx.assert_editor_state(indoc! {"
25852        echo \"test\";
25853        ˇ
25854    "});
25855}
25856
25857fn empty_range(row: usize, column: usize) -> Range<DisplayPoint> {
25858    let point = DisplayPoint::new(DisplayRow(row as u32), column as u32);
25859    point..point
25860}
25861
25862#[track_caller]
25863fn assert_selection_ranges(marked_text: &str, editor: &mut Editor, cx: &mut Context<Editor>) {
25864    let (text, ranges) = marked_text_ranges(marked_text, true);
25865    assert_eq!(editor.text(cx), text);
25866    assert_eq!(
25867        editor.selections.ranges(&editor.display_snapshot(cx)),
25868        ranges
25869            .iter()
25870            .map(|range| MultiBufferOffset(range.start)..MultiBufferOffset(range.end))
25871            .collect::<Vec<_>>(),
25872        "Assert selections are {}",
25873        marked_text
25874    );
25875}
25876
25877pub fn handle_signature_help_request(
25878    cx: &mut EditorLspTestContext,
25879    mocked_response: lsp::SignatureHelp,
25880) -> impl Future<Output = ()> + use<> {
25881    let mut request =
25882        cx.set_request_handler::<lsp::request::SignatureHelpRequest, _, _>(move |_, _, _| {
25883            let mocked_response = mocked_response.clone();
25884            async move { Ok(Some(mocked_response)) }
25885        });
25886
25887    async move {
25888        request.next().await;
25889    }
25890}
25891
25892#[track_caller]
25893pub fn check_displayed_completions(expected: Vec<&'static str>, cx: &mut EditorLspTestContext) {
25894    cx.update_editor(|editor, _, _| {
25895        if let Some(CodeContextMenu::Completions(menu)) = editor.context_menu.borrow().as_ref() {
25896            let entries = menu.entries.borrow();
25897            let entries = entries
25898                .iter()
25899                .map(|entry| entry.string.as_str())
25900                .collect::<Vec<_>>();
25901            assert_eq!(entries, expected);
25902        } else {
25903            panic!("Expected completions menu");
25904        }
25905    });
25906}
25907
25908#[gpui::test]
25909async fn test_mixed_completions_with_multi_word_snippet(cx: &mut TestAppContext) {
25910    init_test(cx, |_| {});
25911    let mut cx = EditorLspTestContext::new_rust(
25912        lsp::ServerCapabilities {
25913            completion_provider: Some(lsp::CompletionOptions {
25914                ..Default::default()
25915            }),
25916            ..Default::default()
25917        },
25918        cx,
25919    )
25920    .await;
25921    cx.lsp
25922        .set_request_handler::<lsp::request::Completion, _, _>(move |_, _| async move {
25923            Ok(Some(lsp::CompletionResponse::Array(vec![
25924                lsp::CompletionItem {
25925                    label: "unsafe".into(),
25926                    text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
25927                        range: lsp::Range {
25928                            start: lsp::Position {
25929                                line: 0,
25930                                character: 9,
25931                            },
25932                            end: lsp::Position {
25933                                line: 0,
25934                                character: 11,
25935                            },
25936                        },
25937                        new_text: "unsafe".to_string(),
25938                    })),
25939                    insert_text_mode: Some(lsp::InsertTextMode::AS_IS),
25940                    ..Default::default()
25941                },
25942            ])))
25943        });
25944
25945    cx.update_editor(|editor, _, cx| {
25946        editor.project().unwrap().update(cx, |project, cx| {
25947            project.snippets().update(cx, |snippets, _cx| {
25948                snippets.add_snippet_for_test(
25949                    None,
25950                    PathBuf::from("test_snippets.json"),
25951                    vec![
25952                        Arc::new(project::snippet_provider::Snippet {
25953                            prefix: vec![
25954                                "unlimited word count".to_string(),
25955                                "unlimit word count".to_string(),
25956                                "unlimited unknown".to_string(),
25957                            ],
25958                            body: "this is many words".to_string(),
25959                            description: Some("description".to_string()),
25960                            name: "multi-word snippet test".to_string(),
25961                        }),
25962                        Arc::new(project::snippet_provider::Snippet {
25963                            prefix: vec!["unsnip".to_string(), "@few".to_string()],
25964                            body: "fewer words".to_string(),
25965                            description: Some("alt description".to_string()),
25966                            name: "other name".to_string(),
25967                        }),
25968                        Arc::new(project::snippet_provider::Snippet {
25969                            prefix: vec!["ab aa".to_string()],
25970                            body: "abcd".to_string(),
25971                            description: None,
25972                            name: "alphabet".to_string(),
25973                        }),
25974                    ],
25975                );
25976            });
25977        })
25978    });
25979
25980    let get_completions = |cx: &mut EditorLspTestContext| {
25981        cx.update_editor(|editor, _, _| match &*editor.context_menu.borrow() {
25982            Some(CodeContextMenu::Completions(context_menu)) => {
25983                let entries = context_menu.entries.borrow();
25984                entries
25985                    .iter()
25986                    .map(|entry| entry.string.clone())
25987                    .collect_vec()
25988            }
25989            _ => vec![],
25990        })
25991    };
25992
25993    // snippets:
25994    //  @foo
25995    //  foo bar
25996    //
25997    // when typing:
25998    //
25999    // when typing:
26000    //  - if I type a symbol "open the completions with snippets only"
26001    //  - if I type a word character "open the completions menu" (if it had been open snippets only, clear it out)
26002    //
26003    // stuff we need:
26004    //  - filtering logic change?
26005    //  - remember how far back the completion started.
26006
26007    let test_cases: &[(&str, &[&str])] = &[
26008        (
26009            "un",
26010            &[
26011                "unsafe",
26012                "unlimit word count",
26013                "unlimited unknown",
26014                "unlimited word count",
26015                "unsnip",
26016            ],
26017        ),
26018        (
26019            "u ",
26020            &[
26021                "unlimit word count",
26022                "unlimited unknown",
26023                "unlimited word count",
26024            ],
26025        ),
26026        ("u a", &["ab aa", "unsafe"]), // unsAfe
26027        (
26028            "u u",
26029            &[
26030                "unsafe",
26031                "unlimit word count",
26032                "unlimited unknown", // ranked highest among snippets
26033                "unlimited word count",
26034                "unsnip",
26035            ],
26036        ),
26037        ("uw c", &["unlimit word count", "unlimited word count"]),
26038        (
26039            "u w",
26040            &[
26041                "unlimit word count",
26042                "unlimited word count",
26043                "unlimited unknown",
26044            ],
26045        ),
26046        ("u w ", &["unlimit word count", "unlimited word count"]),
26047        (
26048            "u ",
26049            &[
26050                "unlimit word count",
26051                "unlimited unknown",
26052                "unlimited word count",
26053            ],
26054        ),
26055        ("wor", &[]),
26056        ("uf", &["unsafe"]),
26057        ("af", &["unsafe"]),
26058        ("afu", &[]),
26059        (
26060            "ue",
26061            &["unsafe", "unlimited unknown", "unlimited word count"],
26062        ),
26063        ("@", &["@few"]),
26064        ("@few", &["@few"]),
26065        ("@ ", &[]),
26066        ("a@", &["@few"]),
26067        ("a@f", &["@few", "unsafe"]),
26068        ("a@fw", &["@few"]),
26069        ("a", &["ab aa", "unsafe"]),
26070        ("aa", &["ab aa"]),
26071        ("aaa", &["ab aa"]),
26072        ("ab", &["ab aa"]),
26073        ("ab ", &["ab aa"]),
26074        ("ab a", &["ab aa", "unsafe"]),
26075        ("ab ab", &["ab aa"]),
26076        ("ab ab aa", &["ab aa"]),
26077    ];
26078
26079    for &(input_to_simulate, expected_completions) in test_cases {
26080        cx.set_state("fn a() { ˇ }\n");
26081        for c in input_to_simulate.split("") {
26082            cx.simulate_input(c);
26083            cx.run_until_parked();
26084        }
26085        let expected_completions = expected_completions
26086            .iter()
26087            .map(|s| s.to_string())
26088            .collect_vec();
26089        assert_eq!(
26090            get_completions(&mut cx),
26091            expected_completions,
26092            "< actual / expected >, input = {input_to_simulate:?}",
26093        );
26094    }
26095}
26096
26097/// Handle completion request passing a marked string specifying where the completion
26098/// should be triggered from using '|' character, what range should be replaced, and what completions
26099/// should be returned using '<' and '>' to delimit the range.
26100///
26101/// Also see `handle_completion_request_with_insert_and_replace`.
26102#[track_caller]
26103pub fn handle_completion_request(
26104    marked_string: &str,
26105    completions: Vec<&'static str>,
26106    is_incomplete: bool,
26107    counter: Arc<AtomicUsize>,
26108    cx: &mut EditorLspTestContext,
26109) -> impl Future<Output = ()> {
26110    let complete_from_marker: TextRangeMarker = '|'.into();
26111    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26112    let (_, mut marked_ranges) = marked_text_ranges_by(
26113        marked_string,
26114        vec![complete_from_marker.clone(), replace_range_marker.clone()],
26115    );
26116
26117    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26118        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26119    ));
26120    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26121    let replace_range =
26122        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26123
26124    let mut request =
26125        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26126            let completions = completions.clone();
26127            counter.fetch_add(1, atomic::Ordering::Release);
26128            async move {
26129                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26130                assert_eq!(
26131                    params.text_document_position.position,
26132                    complete_from_position
26133                );
26134                Ok(Some(lsp::CompletionResponse::List(lsp::CompletionList {
26135                    is_incomplete,
26136                    item_defaults: None,
26137                    items: completions
26138                        .iter()
26139                        .map(|completion_text| lsp::CompletionItem {
26140                            label: completion_text.to_string(),
26141                            text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
26142                                range: replace_range,
26143                                new_text: completion_text.to_string(),
26144                            })),
26145                            ..Default::default()
26146                        })
26147                        .collect(),
26148                })))
26149            }
26150        });
26151
26152    async move {
26153        request.next().await;
26154    }
26155}
26156
26157/// Similar to `handle_completion_request`, but a [`CompletionTextEdit::InsertAndReplace`] will be
26158/// given instead, which also contains an `insert` range.
26159///
26160/// This function uses markers to define ranges:
26161/// - `|` marks the cursor position
26162/// - `<>` marks the replace range
26163/// - `[]` marks the insert range (optional, defaults to `replace_range.start..cursor_pos`which is what Rust-Analyzer provides)
26164pub fn handle_completion_request_with_insert_and_replace(
26165    cx: &mut EditorLspTestContext,
26166    marked_string: &str,
26167    completions: Vec<(&'static str, &'static str)>, // (label, new_text)
26168    counter: Arc<AtomicUsize>,
26169) -> impl Future<Output = ()> {
26170    let complete_from_marker: TextRangeMarker = '|'.into();
26171    let replace_range_marker: TextRangeMarker = ('<', '>').into();
26172    let insert_range_marker: TextRangeMarker = ('{', '}').into();
26173
26174    let (_, mut marked_ranges) = marked_text_ranges_by(
26175        marked_string,
26176        vec![
26177            complete_from_marker.clone(),
26178            replace_range_marker.clone(),
26179            insert_range_marker.clone(),
26180        ],
26181    );
26182
26183    let complete_from_position = cx.to_lsp(MultiBufferOffset(
26184        marked_ranges.remove(&complete_from_marker).unwrap()[0].start,
26185    ));
26186    let range = marked_ranges.remove(&replace_range_marker).unwrap()[0].clone();
26187    let replace_range =
26188        cx.to_lsp_range(MultiBufferOffset(range.start)..MultiBufferOffset(range.end));
26189
26190    let insert_range = match marked_ranges.remove(&insert_range_marker) {
26191        Some(ranges) if !ranges.is_empty() => {
26192            let range1 = ranges[0].clone();
26193            cx.to_lsp_range(MultiBufferOffset(range1.start)..MultiBufferOffset(range1.end))
26194        }
26195        _ => lsp::Range {
26196            start: replace_range.start,
26197            end: complete_from_position,
26198        },
26199    };
26200
26201    let mut request =
26202        cx.set_request_handler::<lsp::request::Completion, _, _>(move |url, params, _| {
26203            let completions = completions.clone();
26204            counter.fetch_add(1, atomic::Ordering::Release);
26205            async move {
26206                assert_eq!(params.text_document_position.text_document.uri, url.clone());
26207                assert_eq!(
26208                    params.text_document_position.position, complete_from_position,
26209                    "marker `|` position doesn't match",
26210                );
26211                Ok(Some(lsp::CompletionResponse::Array(
26212                    completions
26213                        .iter()
26214                        .map(|(label, new_text)| lsp::CompletionItem {
26215                            label: label.to_string(),
26216                            text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
26217                                lsp::InsertReplaceEdit {
26218                                    insert: insert_range,
26219                                    replace: replace_range,
26220                                    new_text: new_text.to_string(),
26221                                },
26222                            )),
26223                            ..Default::default()
26224                        })
26225                        .collect(),
26226                )))
26227            }
26228        });
26229
26230    async move {
26231        request.next().await;
26232    }
26233}
26234
26235fn handle_resolve_completion_request(
26236    cx: &mut EditorLspTestContext,
26237    edits: Option<Vec<(&'static str, &'static str)>>,
26238) -> impl Future<Output = ()> {
26239    let edits = edits.map(|edits| {
26240        edits
26241            .iter()
26242            .map(|(marked_string, new_text)| {
26243                let (_, marked_ranges) = marked_text_ranges(marked_string, false);
26244                let replace_range = cx.to_lsp_range(
26245                    MultiBufferOffset(marked_ranges[0].start)
26246                        ..MultiBufferOffset(marked_ranges[0].end),
26247                );
26248                lsp::TextEdit::new(replace_range, new_text.to_string())
26249            })
26250            .collect::<Vec<_>>()
26251    });
26252
26253    let mut request =
26254        cx.set_request_handler::<lsp::request::ResolveCompletionItem, _, _>(move |_, _, _| {
26255            let edits = edits.clone();
26256            async move {
26257                Ok(lsp::CompletionItem {
26258                    additional_text_edits: edits,
26259                    ..Default::default()
26260                })
26261            }
26262        });
26263
26264    async move {
26265        request.next().await;
26266    }
26267}
26268
26269pub(crate) fn update_test_language_settings(
26270    cx: &mut TestAppContext,
26271    f: impl Fn(&mut AllLanguageSettingsContent),
26272) {
26273    cx.update(|cx| {
26274        SettingsStore::update_global(cx, |store, cx| {
26275            store.update_user_settings(cx, |settings| f(&mut settings.project.all_languages));
26276        });
26277    });
26278}
26279
26280pub(crate) fn update_test_project_settings(
26281    cx: &mut TestAppContext,
26282    f: impl Fn(&mut ProjectSettingsContent),
26283) {
26284    cx.update(|cx| {
26285        SettingsStore::update_global(cx, |store, cx| {
26286            store.update_user_settings(cx, |settings| f(&mut settings.project));
26287        });
26288    });
26289}
26290
26291pub(crate) fn update_test_editor_settings(
26292    cx: &mut TestAppContext,
26293    f: impl Fn(&mut EditorSettingsContent),
26294) {
26295    cx.update(|cx| {
26296        SettingsStore::update_global(cx, |store, cx| {
26297            store.update_user_settings(cx, |settings| f(&mut settings.editor));
26298        })
26299    })
26300}
26301
26302pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsContent)) {
26303    cx.update(|cx| {
26304        assets::Assets.load_test_fonts(cx);
26305        let store = SettingsStore::test(cx);
26306        cx.set_global(store);
26307        theme::init(theme::LoadThemes::JustBase, cx);
26308        release_channel::init(semver::Version::new(0, 0, 0), cx);
26309        crate::init(cx);
26310    });
26311    zlog::init_test();
26312    update_test_language_settings(cx, f);
26313}
26314
26315#[track_caller]
26316fn assert_hunk_revert(
26317    not_reverted_text_with_selections: &str,
26318    expected_hunk_statuses_before: Vec<DiffHunkStatusKind>,
26319    expected_reverted_text_with_selections: &str,
26320    base_text: &str,
26321    cx: &mut EditorLspTestContext,
26322) {
26323    cx.set_state(not_reverted_text_with_selections);
26324    cx.set_head_text(base_text);
26325    cx.executor().run_until_parked();
26326
26327    let actual_hunk_statuses_before = cx.update_editor(|editor, window, cx| {
26328        let snapshot = editor.snapshot(window, cx);
26329        let reverted_hunk_statuses = snapshot
26330            .buffer_snapshot()
26331            .diff_hunks_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
26332            .map(|hunk| hunk.status().kind)
26333            .collect::<Vec<_>>();
26334
26335        editor.git_restore(&Default::default(), window, cx);
26336        reverted_hunk_statuses
26337    });
26338    cx.executor().run_until_parked();
26339    cx.assert_editor_state(expected_reverted_text_with_selections);
26340    assert_eq!(actual_hunk_statuses_before, expected_hunk_statuses_before);
26341}
26342
26343#[gpui::test(iterations = 10)]
26344async fn test_pulling_diagnostics(cx: &mut TestAppContext) {
26345    init_test(cx, |_| {});
26346
26347    let diagnostic_requests = Arc::new(AtomicUsize::new(0));
26348    let counter = diagnostic_requests.clone();
26349
26350    let fs = FakeFs::new(cx.executor());
26351    fs.insert_tree(
26352        path!("/a"),
26353        json!({
26354            "first.rs": "fn main() { let a = 5; }",
26355            "second.rs": "// Test file",
26356        }),
26357    )
26358    .await;
26359
26360    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26361    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26362    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26363
26364    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26365    language_registry.add(rust_lang());
26366    let mut fake_servers = language_registry.register_fake_lsp(
26367        "Rust",
26368        FakeLspAdapter {
26369            capabilities: lsp::ServerCapabilities {
26370                diagnostic_provider: Some(lsp::DiagnosticServerCapabilities::Options(
26371                    lsp::DiagnosticOptions {
26372                        identifier: None,
26373                        inter_file_dependencies: true,
26374                        workspace_diagnostics: true,
26375                        work_done_progress_options: Default::default(),
26376                    },
26377                )),
26378                ..Default::default()
26379            },
26380            ..Default::default()
26381        },
26382    );
26383
26384    let editor = workspace
26385        .update(cx, |workspace, window, cx| {
26386            workspace.open_abs_path(
26387                PathBuf::from(path!("/a/first.rs")),
26388                OpenOptions::default(),
26389                window,
26390                cx,
26391            )
26392        })
26393        .unwrap()
26394        .await
26395        .unwrap()
26396        .downcast::<Editor>()
26397        .unwrap();
26398    let fake_server = fake_servers.next().await.unwrap();
26399    let server_id = fake_server.server.server_id();
26400    let mut first_request = fake_server
26401        .set_request_handler::<lsp::request::DocumentDiagnosticRequest, _, _>(move |params, _| {
26402            let new_result_id = counter.fetch_add(1, atomic::Ordering::Release) + 1;
26403            let result_id = Some(new_result_id.to_string());
26404            assert_eq!(
26405                params.text_document.uri,
26406                lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26407            );
26408            async move {
26409                Ok(lsp::DocumentDiagnosticReportResult::Report(
26410                    lsp::DocumentDiagnosticReport::Full(lsp::RelatedFullDocumentDiagnosticReport {
26411                        related_documents: None,
26412                        full_document_diagnostic_report: lsp::FullDocumentDiagnosticReport {
26413                            items: Vec::new(),
26414                            result_id,
26415                        },
26416                    }),
26417                ))
26418            }
26419        });
26420
26421    let ensure_result_id = |expected: Option<String>, cx: &mut TestAppContext| {
26422        project.update(cx, |project, cx| {
26423            let buffer_id = editor
26424                .read(cx)
26425                .buffer()
26426                .read(cx)
26427                .as_singleton()
26428                .expect("created a singleton buffer")
26429                .read(cx)
26430                .remote_id();
26431            let buffer_result_id = project
26432                .lsp_store()
26433                .read(cx)
26434                .result_id(server_id, buffer_id, cx);
26435            assert_eq!(expected, buffer_result_id);
26436        });
26437    };
26438
26439    ensure_result_id(None, cx);
26440    cx.executor().advance_clock(Duration::from_millis(60));
26441    cx.executor().run_until_parked();
26442    assert_eq!(
26443        diagnostic_requests.load(atomic::Ordering::Acquire),
26444        1,
26445        "Opening file should trigger diagnostic request"
26446    );
26447    first_request
26448        .next()
26449        .await
26450        .expect("should have sent the first diagnostics pull request");
26451    ensure_result_id(Some("1".to_string()), cx);
26452
26453    // Editing should trigger diagnostics
26454    editor.update_in(cx, |editor, window, cx| {
26455        editor.handle_input("2", window, cx)
26456    });
26457    cx.executor().advance_clock(Duration::from_millis(60));
26458    cx.executor().run_until_parked();
26459    assert_eq!(
26460        diagnostic_requests.load(atomic::Ordering::Acquire),
26461        2,
26462        "Editing should trigger diagnostic request"
26463    );
26464    ensure_result_id(Some("2".to_string()), cx);
26465
26466    // Moving cursor should not trigger diagnostic request
26467    editor.update_in(cx, |editor, window, cx| {
26468        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
26469            s.select_ranges([Point::new(0, 0)..Point::new(0, 0)])
26470        });
26471    });
26472    cx.executor().advance_clock(Duration::from_millis(60));
26473    cx.executor().run_until_parked();
26474    assert_eq!(
26475        diagnostic_requests.load(atomic::Ordering::Acquire),
26476        2,
26477        "Cursor movement should not trigger diagnostic request"
26478    );
26479    ensure_result_id(Some("2".to_string()), cx);
26480    // Multiple rapid edits should be debounced
26481    for _ in 0..5 {
26482        editor.update_in(cx, |editor, window, cx| {
26483            editor.handle_input("x", window, cx)
26484        });
26485    }
26486    cx.executor().advance_clock(Duration::from_millis(60));
26487    cx.executor().run_until_parked();
26488
26489    let final_requests = diagnostic_requests.load(atomic::Ordering::Acquire);
26490    assert!(
26491        final_requests <= 4,
26492        "Multiple rapid edits should be debounced (got {final_requests} requests)",
26493    );
26494    ensure_result_id(Some(final_requests.to_string()), cx);
26495}
26496
26497#[gpui::test]
26498async fn test_add_selection_after_moving_with_multiple_cursors(cx: &mut TestAppContext) {
26499    // Regression test for issue #11671
26500    // Previously, adding a cursor after moving multiple cursors would reset
26501    // the cursor count instead of adding to the existing cursors.
26502    init_test(cx, |_| {});
26503    let mut cx = EditorTestContext::new(cx).await;
26504
26505    // Create a simple buffer with cursor at start
26506    cx.set_state(indoc! {"
26507        ˇaaaa
26508        bbbb
26509        cccc
26510        dddd
26511        eeee
26512        ffff
26513        gggg
26514        hhhh"});
26515
26516    // Add 2 cursors below (so we have 3 total)
26517    cx.update_editor(|editor, window, cx| {
26518        editor.add_selection_below(&Default::default(), window, cx);
26519        editor.add_selection_below(&Default::default(), window, cx);
26520    });
26521
26522    // Verify we have 3 cursors
26523    let initial_count = cx.update_editor(|editor, _, _| editor.selections.count());
26524    assert_eq!(
26525        initial_count, 3,
26526        "Should have 3 cursors after adding 2 below"
26527    );
26528
26529    // Move down one line
26530    cx.update_editor(|editor, window, cx| {
26531        editor.move_down(&MoveDown, window, cx);
26532    });
26533
26534    // Add another cursor below
26535    cx.update_editor(|editor, window, cx| {
26536        editor.add_selection_below(&Default::default(), window, cx);
26537    });
26538
26539    // Should now have 4 cursors (3 original + 1 new)
26540    let final_count = cx.update_editor(|editor, _, _| editor.selections.count());
26541    assert_eq!(
26542        final_count, 4,
26543        "Should have 4 cursors after moving and adding another"
26544    );
26545}
26546
26547#[gpui::test]
26548async fn test_add_selection_skip_soft_wrap_option(cx: &mut TestAppContext) {
26549    init_test(cx, |_| {});
26550
26551    let mut cx = EditorTestContext::new(cx).await;
26552
26553    cx.set_state(indoc!(
26554        r#"ˇThis is a very long line that will be wrapped when soft wrapping is enabled
26555           Second line here"#
26556    ));
26557
26558    cx.update_editor(|editor, window, cx| {
26559        // Enable soft wrapping with a narrow width to force soft wrapping and
26560        // confirm that more than 2 rows are being displayed.
26561        editor.set_wrap_width(Some(100.0.into()), cx);
26562        assert!(editor.display_text(cx).lines().count() > 2);
26563
26564        editor.add_selection_below(
26565            &AddSelectionBelow {
26566                skip_soft_wrap: true,
26567            },
26568            window,
26569            cx,
26570        );
26571
26572        assert_eq!(
26573            display_ranges(editor, cx),
26574            &[
26575                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26576                DisplayPoint::new(DisplayRow(8), 0)..DisplayPoint::new(DisplayRow(8), 0),
26577            ]
26578        );
26579
26580        editor.add_selection_above(
26581            &AddSelectionAbove {
26582                skip_soft_wrap: true,
26583            },
26584            window,
26585            cx,
26586        );
26587
26588        assert_eq!(
26589            display_ranges(editor, cx),
26590            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26591        );
26592
26593        editor.add_selection_below(
26594            &AddSelectionBelow {
26595                skip_soft_wrap: false,
26596            },
26597            window,
26598            cx,
26599        );
26600
26601        assert_eq!(
26602            display_ranges(editor, cx),
26603            &[
26604                DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0),
26605                DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0),
26606            ]
26607        );
26608
26609        editor.add_selection_above(
26610            &AddSelectionAbove {
26611                skip_soft_wrap: false,
26612            },
26613            window,
26614            cx,
26615        );
26616
26617        assert_eq!(
26618            display_ranges(editor, cx),
26619            &[DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)]
26620        );
26621    });
26622}
26623
26624#[gpui::test(iterations = 10)]
26625async fn test_document_colors(cx: &mut TestAppContext) {
26626    let expected_color = Rgba {
26627        r: 0.33,
26628        g: 0.33,
26629        b: 0.33,
26630        a: 0.33,
26631    };
26632
26633    init_test(cx, |_| {});
26634
26635    let fs = FakeFs::new(cx.executor());
26636    fs.insert_tree(
26637        path!("/a"),
26638        json!({
26639            "first.rs": "fn main() { let a = 5; }",
26640        }),
26641    )
26642    .await;
26643
26644    let project = Project::test(fs, [path!("/a").as_ref()], cx).await;
26645    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
26646    let cx = &mut VisualTestContext::from_window(*workspace, cx);
26647
26648    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
26649    language_registry.add(rust_lang());
26650    let mut fake_servers = language_registry.register_fake_lsp(
26651        "Rust",
26652        FakeLspAdapter {
26653            capabilities: lsp::ServerCapabilities {
26654                color_provider: Some(lsp::ColorProviderCapability::Simple(true)),
26655                ..lsp::ServerCapabilities::default()
26656            },
26657            name: "rust-analyzer",
26658            ..FakeLspAdapter::default()
26659        },
26660    );
26661    let mut fake_servers_without_capabilities = language_registry.register_fake_lsp(
26662        "Rust",
26663        FakeLspAdapter {
26664            capabilities: lsp::ServerCapabilities {
26665                color_provider: Some(lsp::ColorProviderCapability::Simple(false)),
26666                ..lsp::ServerCapabilities::default()
26667            },
26668            name: "not-rust-analyzer",
26669            ..FakeLspAdapter::default()
26670        },
26671    );
26672
26673    let editor = workspace
26674        .update(cx, |workspace, window, cx| {
26675            workspace.open_abs_path(
26676                PathBuf::from(path!("/a/first.rs")),
26677                OpenOptions::default(),
26678                window,
26679                cx,
26680            )
26681        })
26682        .unwrap()
26683        .await
26684        .unwrap()
26685        .downcast::<Editor>()
26686        .unwrap();
26687    let fake_language_server = fake_servers.next().await.unwrap();
26688    let fake_language_server_without_capabilities =
26689        fake_servers_without_capabilities.next().await.unwrap();
26690    let requests_made = Arc::new(AtomicUsize::new(0));
26691    let closure_requests_made = Arc::clone(&requests_made);
26692    let mut color_request_handle = fake_language_server
26693        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26694            let requests_made = Arc::clone(&closure_requests_made);
26695            async move {
26696                assert_eq!(
26697                    params.text_document.uri,
26698                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26699                );
26700                requests_made.fetch_add(1, atomic::Ordering::Release);
26701                Ok(vec![
26702                    lsp::ColorInformation {
26703                        range: lsp::Range {
26704                            start: lsp::Position {
26705                                line: 0,
26706                                character: 0,
26707                            },
26708                            end: lsp::Position {
26709                                line: 0,
26710                                character: 1,
26711                            },
26712                        },
26713                        color: lsp::Color {
26714                            red: 0.33,
26715                            green: 0.33,
26716                            blue: 0.33,
26717                            alpha: 0.33,
26718                        },
26719                    },
26720                    lsp::ColorInformation {
26721                        range: lsp::Range {
26722                            start: lsp::Position {
26723                                line: 0,
26724                                character: 0,
26725                            },
26726                            end: lsp::Position {
26727                                line: 0,
26728                                character: 1,
26729                            },
26730                        },
26731                        color: lsp::Color {
26732                            red: 0.33,
26733                            green: 0.33,
26734                            blue: 0.33,
26735                            alpha: 0.33,
26736                        },
26737                    },
26738                ])
26739            }
26740        });
26741
26742    let _handle = fake_language_server_without_capabilities
26743        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |_, _| async move {
26744            panic!("Should not be called");
26745        });
26746    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26747    color_request_handle.next().await.unwrap();
26748    cx.run_until_parked();
26749    assert_eq!(
26750        1,
26751        requests_made.load(atomic::Ordering::Acquire),
26752        "Should query for colors once per editor open"
26753    );
26754    editor.update_in(cx, |editor, _, cx| {
26755        assert_eq!(
26756            vec![expected_color],
26757            extract_color_inlays(editor, cx),
26758            "Should have an initial inlay"
26759        );
26760    });
26761
26762    // opening another file in a split should not influence the LSP query counter
26763    workspace
26764        .update(cx, |workspace, window, cx| {
26765            assert_eq!(
26766                workspace.panes().len(),
26767                1,
26768                "Should have one pane with one editor"
26769            );
26770            workspace.move_item_to_pane_in_direction(
26771                &MoveItemToPaneInDirection {
26772                    direction: SplitDirection::Right,
26773                    focus: false,
26774                    clone: true,
26775                },
26776                window,
26777                cx,
26778            );
26779        })
26780        .unwrap();
26781    cx.run_until_parked();
26782    workspace
26783        .update(cx, |workspace, _, cx| {
26784            let panes = workspace.panes();
26785            assert_eq!(panes.len(), 2, "Should have two panes after splitting");
26786            for pane in panes {
26787                let editor = pane
26788                    .read(cx)
26789                    .active_item()
26790                    .and_then(|item| item.downcast::<Editor>())
26791                    .expect("Should have opened an editor in each split");
26792                let editor_file = editor
26793                    .read(cx)
26794                    .buffer()
26795                    .read(cx)
26796                    .as_singleton()
26797                    .expect("test deals with singleton buffers")
26798                    .read(cx)
26799                    .file()
26800                    .expect("test buffese should have a file")
26801                    .path();
26802                assert_eq!(
26803                    editor_file.as_ref(),
26804                    rel_path("first.rs"),
26805                    "Both editors should be opened for the same file"
26806                )
26807            }
26808        })
26809        .unwrap();
26810
26811    cx.executor().advance_clock(Duration::from_millis(500));
26812    let save = editor.update_in(cx, |editor, window, cx| {
26813        editor.move_to_end(&MoveToEnd, window, cx);
26814        editor.handle_input("dirty", window, cx);
26815        editor.save(
26816            SaveOptions {
26817                format: true,
26818                autosave: true,
26819            },
26820            project.clone(),
26821            window,
26822            cx,
26823        )
26824    });
26825    save.await.unwrap();
26826
26827    color_request_handle.next().await.unwrap();
26828    cx.run_until_parked();
26829    assert_eq!(
26830        2,
26831        requests_made.load(atomic::Ordering::Acquire),
26832        "Should query for colors once per save (deduplicated) and once per formatting after save"
26833    );
26834
26835    drop(editor);
26836    let close = workspace
26837        .update(cx, |workspace, window, cx| {
26838            workspace.active_pane().update(cx, |pane, cx| {
26839                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26840            })
26841        })
26842        .unwrap();
26843    close.await.unwrap();
26844    let close = workspace
26845        .update(cx, |workspace, window, cx| {
26846            workspace.active_pane().update(cx, |pane, cx| {
26847                pane.close_active_item(&CloseActiveItem::default(), window, cx)
26848            })
26849        })
26850        .unwrap();
26851    close.await.unwrap();
26852    assert_eq!(
26853        2,
26854        requests_made.load(atomic::Ordering::Acquire),
26855        "After saving and closing all editors, no extra requests should be made"
26856    );
26857    workspace
26858        .update(cx, |workspace, _, cx| {
26859            assert!(
26860                workspace.active_item(cx).is_none(),
26861                "Should close all editors"
26862            )
26863        })
26864        .unwrap();
26865
26866    workspace
26867        .update(cx, |workspace, window, cx| {
26868            workspace.active_pane().update(cx, |pane, cx| {
26869                pane.navigate_backward(&workspace::GoBack, window, cx);
26870            })
26871        })
26872        .unwrap();
26873    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26874    cx.run_until_parked();
26875    let editor = workspace
26876        .update(cx, |workspace, _, cx| {
26877            workspace
26878                .active_item(cx)
26879                .expect("Should have reopened the editor again after navigating back")
26880                .downcast::<Editor>()
26881                .expect("Should be an editor")
26882        })
26883        .unwrap();
26884
26885    assert_eq!(
26886        2,
26887        requests_made.load(atomic::Ordering::Acquire),
26888        "Cache should be reused on buffer close and reopen"
26889    );
26890    editor.update(cx, |editor, cx| {
26891        assert_eq!(
26892            vec![expected_color],
26893            extract_color_inlays(editor, cx),
26894            "Should have an initial inlay"
26895        );
26896    });
26897
26898    drop(color_request_handle);
26899    let closure_requests_made = Arc::clone(&requests_made);
26900    let mut empty_color_request_handle = fake_language_server
26901        .set_request_handler::<lsp::request::DocumentColor, _, _>(move |params, _| {
26902            let requests_made = Arc::clone(&closure_requests_made);
26903            async move {
26904                assert_eq!(
26905                    params.text_document.uri,
26906                    lsp::Uri::from_file_path(path!("/a/first.rs")).unwrap()
26907                );
26908                requests_made.fetch_add(1, atomic::Ordering::Release);
26909                Ok(Vec::new())
26910            }
26911        });
26912    let save = editor.update_in(cx, |editor, window, cx| {
26913        editor.move_to_end(&MoveToEnd, window, cx);
26914        editor.handle_input("dirty_again", window, cx);
26915        editor.save(
26916            SaveOptions {
26917                format: false,
26918                autosave: true,
26919            },
26920            project.clone(),
26921            window,
26922            cx,
26923        )
26924    });
26925    save.await.unwrap();
26926
26927    cx.executor().advance_clock(FETCH_COLORS_DEBOUNCE_TIMEOUT);
26928    empty_color_request_handle.next().await.unwrap();
26929    cx.run_until_parked();
26930    assert_eq!(
26931        3,
26932        requests_made.load(atomic::Ordering::Acquire),
26933        "Should query for colors once per save only, as formatting was not requested"
26934    );
26935    editor.update(cx, |editor, cx| {
26936        assert_eq!(
26937            Vec::<Rgba>::new(),
26938            extract_color_inlays(editor, cx),
26939            "Should clear all colors when the server returns an empty response"
26940        );
26941    });
26942}
26943
26944#[gpui::test]
26945async fn test_newline_replacement_in_single_line(cx: &mut TestAppContext) {
26946    init_test(cx, |_| {});
26947    let (editor, cx) = cx.add_window_view(Editor::single_line);
26948    editor.update_in(cx, |editor, window, cx| {
26949        editor.set_text("oops\n\nwow\n", window, cx)
26950    });
26951    cx.run_until_parked();
26952    editor.update(cx, |editor, cx| {
26953        assert_eq!(editor.display_text(cx), "oops⋯⋯wow⋯");
26954    });
26955    editor.update(cx, |editor, cx| {
26956        editor.edit([(MultiBufferOffset(3)..MultiBufferOffset(5), "")], cx)
26957    });
26958    cx.run_until_parked();
26959    editor.update(cx, |editor, cx| {
26960        assert_eq!(editor.display_text(cx), "oop⋯wow⋯");
26961    });
26962}
26963
26964#[gpui::test]
26965async fn test_non_utf_8_opens(cx: &mut TestAppContext) {
26966    init_test(cx, |_| {});
26967
26968    cx.update(|cx| {
26969        register_project_item::<Editor>(cx);
26970    });
26971
26972    let fs = FakeFs::new(cx.executor());
26973    fs.insert_tree("/root1", json!({})).await;
26974    fs.insert_file("/root1/one.pdf", vec![0xff, 0xfe, 0xfd])
26975        .await;
26976
26977    let project = Project::test(fs, ["/root1".as_ref()], cx).await;
26978    let (workspace, cx) =
26979        cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
26980
26981    let worktree_id = project.update(cx, |project, cx| {
26982        project.worktrees(cx).next().unwrap().read(cx).id()
26983    });
26984
26985    let handle = workspace
26986        .update_in(cx, |workspace, window, cx| {
26987            let project_path = (worktree_id, rel_path("one.pdf"));
26988            workspace.open_path(project_path, None, true, window, cx)
26989        })
26990        .await
26991        .unwrap();
26992
26993    assert_eq!(
26994        handle.to_any_view().entity_type(),
26995        TypeId::of::<InvalidItemView>()
26996    );
26997}
26998
26999#[gpui::test]
27000async fn test_select_next_prev_syntax_node(cx: &mut TestAppContext) {
27001    init_test(cx, |_| {});
27002
27003    let language = Arc::new(Language::new(
27004        LanguageConfig::default(),
27005        Some(tree_sitter_rust::LANGUAGE.into()),
27006    ));
27007
27008    // Test hierarchical sibling navigation
27009    let text = r#"
27010        fn outer() {
27011            if condition {
27012                let a = 1;
27013            }
27014            let b = 2;
27015        }
27016
27017        fn another() {
27018            let c = 3;
27019        }
27020    "#;
27021
27022    let buffer = cx.new(|cx| Buffer::local(text, cx).with_language_immediate(language, cx));
27023    let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
27024    let (editor, cx) = cx.add_window_view(|window, cx| build_editor(buffer, window, cx));
27025
27026    // Wait for parsing to complete
27027    editor
27028        .condition::<crate::EditorEvent>(cx, |editor, cx| !editor.buffer.read(cx).is_parsing(cx))
27029        .await;
27030
27031    editor.update_in(cx, |editor, window, cx| {
27032        // Start by selecting "let a = 1;" inside the if block
27033        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27034            s.select_display_ranges([
27035                DisplayPoint::new(DisplayRow(3), 16)..DisplayPoint::new(DisplayRow(3), 26)
27036            ]);
27037        });
27038
27039        let initial_selection = editor
27040            .selections
27041            .display_ranges(&editor.display_snapshot(cx));
27042        assert_eq!(initial_selection.len(), 1, "Should have one selection");
27043
27044        // Test select next sibling - should move up levels to find the next sibling
27045        // Since "let a = 1;" has no siblings in the if block, it should move up
27046        // to find "let b = 2;" which is a sibling of the if block
27047        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27048        let next_selection = editor
27049            .selections
27050            .display_ranges(&editor.display_snapshot(cx));
27051
27052        // Should have a selection and it should be different from the initial
27053        assert_eq!(
27054            next_selection.len(),
27055            1,
27056            "Should have one selection after next"
27057        );
27058        assert_ne!(
27059            next_selection[0], initial_selection[0],
27060            "Next sibling selection should be different"
27061        );
27062
27063        // Test hierarchical navigation by going to the end of the current function
27064        // and trying to navigate to the next function
27065        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27066            s.select_display_ranges([
27067                DisplayPoint::new(DisplayRow(5), 12)..DisplayPoint::new(DisplayRow(5), 22)
27068            ]);
27069        });
27070
27071        editor.select_next_syntax_node(&SelectNextSyntaxNode, window, cx);
27072        let function_next_selection = editor
27073            .selections
27074            .display_ranges(&editor.display_snapshot(cx));
27075
27076        // Should move to the next function
27077        assert_eq!(
27078            function_next_selection.len(),
27079            1,
27080            "Should have one selection after function next"
27081        );
27082
27083        // Test select previous sibling navigation
27084        editor.select_prev_syntax_node(&SelectPreviousSyntaxNode, window, cx);
27085        let prev_selection = editor
27086            .selections
27087            .display_ranges(&editor.display_snapshot(cx));
27088
27089        // Should have a selection and it should be different
27090        assert_eq!(
27091            prev_selection.len(),
27092            1,
27093            "Should have one selection after prev"
27094        );
27095        assert_ne!(
27096            prev_selection[0], function_next_selection[0],
27097            "Previous sibling selection should be different from next"
27098        );
27099    });
27100}
27101
27102#[gpui::test]
27103async fn test_next_prev_document_highlight(cx: &mut TestAppContext) {
27104    init_test(cx, |_| {});
27105
27106    let mut cx = EditorTestContext::new(cx).await;
27107    cx.set_state(
27108        "let ˇvariable = 42;
27109let another = variable + 1;
27110let result = variable * 2;",
27111    );
27112
27113    // Set up document highlights manually (simulating LSP response)
27114    cx.update_editor(|editor, _window, cx| {
27115        let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
27116
27117        // Create highlights for "variable" occurrences
27118        let highlight_ranges = [
27119            Point::new(0, 4)..Point::new(0, 12),  // First "variable"
27120            Point::new(1, 14)..Point::new(1, 22), // Second "variable"
27121            Point::new(2, 13)..Point::new(2, 21), // Third "variable"
27122        ];
27123
27124        let anchor_ranges: Vec<_> = highlight_ranges
27125            .iter()
27126            .map(|range| range.clone().to_anchors(&buffer_snapshot))
27127            .collect();
27128
27129        editor.highlight_background::<DocumentHighlightRead>(
27130            &anchor_ranges,
27131            |theme| theme.colors().editor_document_highlight_read_background,
27132            cx,
27133        );
27134    });
27135
27136    // Go to next highlight - should move to second "variable"
27137    cx.update_editor(|editor, window, cx| {
27138        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27139    });
27140    cx.assert_editor_state(
27141        "let variable = 42;
27142let another = ˇvariable + 1;
27143let result = variable * 2;",
27144    );
27145
27146    // Go to next highlight - should move to third "variable"
27147    cx.update_editor(|editor, window, cx| {
27148        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27149    });
27150    cx.assert_editor_state(
27151        "let variable = 42;
27152let another = variable + 1;
27153let result = ˇvariable * 2;",
27154    );
27155
27156    // Go to next highlight - should stay at third "variable" (no wrap-around)
27157    cx.update_editor(|editor, window, cx| {
27158        editor.go_to_next_document_highlight(&GoToNextDocumentHighlight, window, cx);
27159    });
27160    cx.assert_editor_state(
27161        "let variable = 42;
27162let another = variable + 1;
27163let result = ˇvariable * 2;",
27164    );
27165
27166    // Now test going backwards from third position
27167    cx.update_editor(|editor, window, cx| {
27168        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27169    });
27170    cx.assert_editor_state(
27171        "let variable = 42;
27172let another = ˇvariable + 1;
27173let result = variable * 2;",
27174    );
27175
27176    // Go to previous highlight - should move to first "variable"
27177    cx.update_editor(|editor, window, cx| {
27178        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27179    });
27180    cx.assert_editor_state(
27181        "let ˇvariable = 42;
27182let another = variable + 1;
27183let result = variable * 2;",
27184    );
27185
27186    // Go to previous highlight - should stay on first "variable"
27187    cx.update_editor(|editor, window, cx| {
27188        editor.go_to_prev_document_highlight(&GoToPreviousDocumentHighlight, window, cx);
27189    });
27190    cx.assert_editor_state(
27191        "let ˇvariable = 42;
27192let another = variable + 1;
27193let result = variable * 2;",
27194    );
27195}
27196
27197#[gpui::test]
27198async fn test_paste_url_from_other_app_creates_markdown_link_over_selected_text(
27199    cx: &mut gpui::TestAppContext,
27200) {
27201    init_test(cx, |_| {});
27202
27203    let url = "https://zed.dev";
27204
27205    let markdown_language = Arc::new(Language::new(
27206        LanguageConfig {
27207            name: "Markdown".into(),
27208            ..LanguageConfig::default()
27209        },
27210        None,
27211    ));
27212
27213    let mut cx = EditorTestContext::new(cx).await;
27214    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
27215    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)");
27216
27217    cx.update_editor(|editor, window, cx| {
27218        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27219        editor.paste(&Paste, window, cx);
27220    });
27221
27222    cx.assert_editor_state(&format!(
27223        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)"
27224    ));
27225}
27226
27227#[gpui::test]
27228async fn test_paste_url_from_zed_copy_creates_markdown_link_over_selected_text(
27229    cx: &mut gpui::TestAppContext,
27230) {
27231    init_test(cx, |_| {});
27232
27233    let url = "https://zed.dev";
27234
27235    let markdown_language = Arc::new(Language::new(
27236        LanguageConfig {
27237            name: "Markdown".into(),
27238            ..LanguageConfig::default()
27239        },
27240        None,
27241    ));
27242
27243    let mut cx = EditorTestContext::new(cx).await;
27244    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
27245    cx.set_state(&format!(
27246        "Hello, editor.\nZed is great (see this link: )\n«{url}ˇ»"
27247    ));
27248
27249    cx.update_editor(|editor, window, cx| {
27250        editor.copy(&Copy, window, cx);
27251    });
27252
27253    cx.set_state(&format!(
27254        "Hello, «editorˇ».\nZed is «ˇgreat» (see this link: ˇ)\n{url}"
27255    ));
27256
27257    cx.update_editor(|editor, window, cx| {
27258        editor.paste(&Paste, window, cx);
27259    });
27260
27261    cx.assert_editor_state(&format!(
27262        "Hello, [editor]({url})ˇ.\nZed is [great]({url})ˇ (see this link: {url}ˇ)\n{url}"
27263    ));
27264}
27265
27266#[gpui::test]
27267async fn test_paste_url_from_other_app_replaces_existing_url_without_creating_markdown_link(
27268    cx: &mut gpui::TestAppContext,
27269) {
27270    init_test(cx, |_| {});
27271
27272    let url = "https://zed.dev";
27273
27274    let markdown_language = Arc::new(Language::new(
27275        LanguageConfig {
27276            name: "Markdown".into(),
27277            ..LanguageConfig::default()
27278        },
27279        None,
27280    ));
27281
27282    let mut cx = EditorTestContext::new(cx).await;
27283    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
27284    cx.set_state("Please visit zed's homepage: «https://www.apple.comˇ»");
27285
27286    cx.update_editor(|editor, window, cx| {
27287        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27288        editor.paste(&Paste, window, cx);
27289    });
27290
27291    cx.assert_editor_state(&format!("Please visit zed's homepage: {url}ˇ"));
27292}
27293
27294#[gpui::test]
27295async fn test_paste_plain_text_from_other_app_replaces_selection_without_creating_markdown_link(
27296    cx: &mut gpui::TestAppContext,
27297) {
27298    init_test(cx, |_| {});
27299
27300    let text = "Awesome";
27301
27302    let markdown_language = Arc::new(Language::new(
27303        LanguageConfig {
27304            name: "Markdown".into(),
27305            ..LanguageConfig::default()
27306        },
27307        None,
27308    ));
27309
27310    let mut cx = EditorTestContext::new(cx).await;
27311    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
27312    cx.set_state("Hello, «editorˇ».\nZed is «ˇgreat»");
27313
27314    cx.update_editor(|editor, window, cx| {
27315        cx.write_to_clipboard(ClipboardItem::new_string(text.to_string()));
27316        editor.paste(&Paste, window, cx);
27317    });
27318
27319    cx.assert_editor_state(&format!("Hello, {text}ˇ.\nZed is {text}ˇ"));
27320}
27321
27322#[gpui::test]
27323async fn test_paste_url_from_other_app_without_creating_markdown_link_in_non_markdown_language(
27324    cx: &mut gpui::TestAppContext,
27325) {
27326    init_test(cx, |_| {});
27327
27328    let url = "https://zed.dev";
27329
27330    let markdown_language = Arc::new(Language::new(
27331        LanguageConfig {
27332            name: "Rust".into(),
27333            ..LanguageConfig::default()
27334        },
27335        None,
27336    ));
27337
27338    let mut cx = EditorTestContext::new(cx).await;
27339    cx.update_buffer(|buffer, cx| buffer.set_language_immediate(Some(markdown_language), cx));
27340    cx.set_state("// Hello, «editorˇ».\n// Zed is «ˇgreat» (see this link: ˇ)");
27341
27342    cx.update_editor(|editor, window, cx| {
27343        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27344        editor.paste(&Paste, window, cx);
27345    });
27346
27347    cx.assert_editor_state(&format!(
27348        "// Hello, {url}ˇ.\n// Zed is {url}ˇ (see this link: {url}ˇ)"
27349    ));
27350}
27351
27352#[gpui::test]
27353async fn test_paste_url_from_other_app_creates_markdown_link_selectively_in_multi_buffer(
27354    cx: &mut TestAppContext,
27355) {
27356    init_test(cx, |_| {});
27357
27358    let url = "https://zed.dev";
27359
27360    let markdown_language = Arc::new(Language::new(
27361        LanguageConfig {
27362            name: "Markdown".into(),
27363            ..LanguageConfig::default()
27364        },
27365        None,
27366    ));
27367
27368    let (editor, cx) = cx.add_window_view(|window, cx| {
27369        let multi_buffer = MultiBuffer::build_multi(
27370            [
27371                ("this will embed -> link", vec![Point::row_range(0..1)]),
27372                ("this will replace -> link", vec![Point::row_range(0..1)]),
27373            ],
27374            cx,
27375        );
27376        let mut editor = Editor::new(EditorMode::full(), multi_buffer.clone(), None, window, cx);
27377        editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27378            s.select_ranges(vec![
27379                Point::new(0, 19)..Point::new(0, 23),
27380                Point::new(1, 21)..Point::new(1, 25),
27381            ])
27382        });
27383        let first_buffer_id = multi_buffer
27384            .read(cx)
27385            .excerpt_buffer_ids()
27386            .into_iter()
27387            .next()
27388            .unwrap();
27389        let first_buffer = multi_buffer.read(cx).buffer(first_buffer_id).unwrap();
27390        first_buffer.update(cx, |buffer, cx| {
27391            buffer.set_language_immediate(Some(markdown_language.clone()), cx);
27392        });
27393
27394        editor
27395    });
27396    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
27397
27398    cx.update_editor(|editor, window, cx| {
27399        cx.write_to_clipboard(ClipboardItem::new_string(url.to_string()));
27400        editor.paste(&Paste, window, cx);
27401    });
27402
27403    cx.assert_editor_state(&format!(
27404        "this will embed -> [link]({url}\nthis will replace -> {url}ˇ"
27405    ));
27406}
27407
27408#[gpui::test]
27409async fn test_race_in_multibuffer_save(cx: &mut TestAppContext) {
27410    init_test(cx, |_| {});
27411
27412    let fs = FakeFs::new(cx.executor());
27413    fs.insert_tree(
27414        path!("/project"),
27415        json!({
27416            "first.rs": "# First Document\nSome content here.",
27417            "second.rs": "Plain text content for second file.",
27418        }),
27419    )
27420    .await;
27421
27422    let project = Project::test(fs, [path!("/project").as_ref()], cx).await;
27423    let workspace = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
27424    let cx = &mut VisualTestContext::from_window(*workspace, cx);
27425
27426    let language = rust_lang();
27427    let language_registry = project.read_with(cx, |project, _| project.languages().clone());
27428    language_registry.add(language.clone());
27429    let mut fake_servers = language_registry.register_fake_lsp(
27430        "Rust",
27431        FakeLspAdapter {
27432            ..FakeLspAdapter::default()
27433        },
27434    );
27435
27436    let buffer1 = project
27437        .update(cx, |project, cx| {
27438            project.open_local_buffer(PathBuf::from(path!("/project/first.rs")), cx)
27439        })
27440        .await
27441        .unwrap();
27442    let buffer2 = project
27443        .update(cx, |project, cx| {
27444            project.open_local_buffer(PathBuf::from(path!("/project/second.rs")), cx)
27445        })
27446        .await
27447        .unwrap();
27448
27449    let multi_buffer = cx.new(|cx| {
27450        let mut multi_buffer = MultiBuffer::new(Capability::ReadWrite);
27451        multi_buffer.set_excerpts_for_path(
27452            PathKey::for_buffer(&buffer1, cx),
27453            buffer1.clone(),
27454            [Point::zero()..buffer1.read(cx).max_point()],
27455            3,
27456            cx,
27457        );
27458        multi_buffer.set_excerpts_for_path(
27459            PathKey::for_buffer(&buffer2, cx),
27460            buffer2.clone(),
27461            [Point::zero()..buffer1.read(cx).max_point()],
27462            3,
27463            cx,
27464        );
27465        multi_buffer
27466    });
27467
27468    let (editor, cx) = cx.add_window_view(|window, cx| {
27469        Editor::new(
27470            EditorMode::full(),
27471            multi_buffer,
27472            Some(project.clone()),
27473            window,
27474            cx,
27475        )
27476    });
27477
27478    let fake_language_server = fake_servers.next().await.unwrap();
27479
27480    buffer1.update(cx, |buffer, cx| buffer.edit([(0..0, "hello!")], None, cx));
27481
27482    let save = editor.update_in(cx, |editor, window, cx| {
27483        assert!(editor.is_dirty(cx));
27484
27485        editor.save(
27486            SaveOptions {
27487                format: true,
27488                autosave: true,
27489            },
27490            project,
27491            window,
27492            cx,
27493        )
27494    });
27495    let (start_edit_tx, start_edit_rx) = oneshot::channel();
27496    let (done_edit_tx, done_edit_rx) = oneshot::channel();
27497    let mut done_edit_rx = Some(done_edit_rx);
27498    let mut start_edit_tx = Some(start_edit_tx);
27499
27500    fake_language_server.set_request_handler::<lsp::request::Formatting, _, _>(move |_, _| {
27501        start_edit_tx.take().unwrap().send(()).unwrap();
27502        let done_edit_rx = done_edit_rx.take().unwrap();
27503        async move {
27504            done_edit_rx.await.unwrap();
27505            Ok(None)
27506        }
27507    });
27508
27509    start_edit_rx.await.unwrap();
27510    buffer2
27511        .update(cx, |buffer, cx| buffer.edit([(0..0, "world!")], None, cx))
27512        .unwrap();
27513
27514    done_edit_tx.send(()).unwrap();
27515
27516    save.await.unwrap();
27517    cx.update(|_, cx| assert!(editor.is_dirty(cx)));
27518}
27519
27520#[track_caller]
27521fn extract_color_inlays(editor: &Editor, cx: &App) -> Vec<Rgba> {
27522    editor
27523        .all_inlays(cx)
27524        .into_iter()
27525        .filter_map(|inlay| inlay.get_color())
27526        .map(Rgba::from)
27527        .collect()
27528}
27529
27530#[gpui::test]
27531fn test_duplicate_line_up_on_last_line_without_newline(cx: &mut TestAppContext) {
27532    init_test(cx, |_| {});
27533
27534    let editor = cx.add_window(|window, cx| {
27535        let buffer = MultiBuffer::build_simple("line1\nline2", cx);
27536        build_editor(buffer, window, cx)
27537    });
27538
27539    editor
27540        .update(cx, |editor, window, cx| {
27541            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
27542                s.select_display_ranges([
27543                    DisplayPoint::new(DisplayRow(1), 0)..DisplayPoint::new(DisplayRow(1), 0)
27544                ])
27545            });
27546
27547            editor.duplicate_line_up(&DuplicateLineUp, window, cx);
27548
27549            assert_eq!(
27550                editor.display_text(cx),
27551                "line1\nline2\nline2",
27552                "Duplicating last line upward should create duplicate above, not on same line"
27553            );
27554
27555            assert_eq!(
27556                editor
27557                    .selections
27558                    .display_ranges(&editor.display_snapshot(cx)),
27559                vec![DisplayPoint::new(DisplayRow(0), 0)..DisplayPoint::new(DisplayRow(0), 0)],
27560                "Selection should move to the duplicated line"
27561            );
27562        })
27563        .unwrap();
27564}
27565
27566#[gpui::test]
27567async fn test_copy_line_without_trailing_newline(cx: &mut TestAppContext) {
27568    init_test(cx, |_| {});
27569
27570    let mut cx = EditorTestContext::new(cx).await;
27571
27572    cx.set_state("line1\nline2ˇ");
27573
27574    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27575
27576    let clipboard_text = cx
27577        .read_from_clipboard()
27578        .and_then(|item| item.text().as_deref().map(str::to_string));
27579
27580    assert_eq!(
27581        clipboard_text,
27582        Some("line2\n".to_string()),
27583        "Copying a line without trailing newline should include a newline"
27584    );
27585
27586    cx.set_state("line1\nˇ");
27587
27588    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27589
27590    cx.assert_editor_state("line1\nline2\nˇ");
27591}
27592
27593#[gpui::test]
27594async fn test_multi_selection_copy_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27595    init_test(cx, |_| {});
27596
27597    let mut cx = EditorTestContext::new(cx).await;
27598
27599    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27600
27601    cx.update_editor(|e, window, cx| e.copy(&Copy, window, cx));
27602
27603    let clipboard_text = cx
27604        .read_from_clipboard()
27605        .and_then(|item| item.text().as_deref().map(str::to_string));
27606
27607    assert_eq!(
27608        clipboard_text,
27609        Some("line1\nline2\nline3\n".to_string()),
27610        "Copying multiple lines should include a single newline between lines"
27611    );
27612
27613    cx.set_state("lineA\nˇ");
27614
27615    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27616
27617    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27618}
27619
27620#[gpui::test]
27621async fn test_multi_selection_cut_with_newline_between_copied_lines(cx: &mut TestAppContext) {
27622    init_test(cx, |_| {});
27623
27624    let mut cx = EditorTestContext::new(cx).await;
27625
27626    cx.set_state("ˇline1\nˇline2\nˇline3\n");
27627
27628    cx.update_editor(|e, window, cx| e.cut(&Cut, window, cx));
27629
27630    let clipboard_text = cx
27631        .read_from_clipboard()
27632        .and_then(|item| item.text().as_deref().map(str::to_string));
27633
27634    assert_eq!(
27635        clipboard_text,
27636        Some("line1\nline2\nline3\n".to_string()),
27637        "Copying multiple lines should include a single newline between lines"
27638    );
27639
27640    cx.set_state("lineA\nˇ");
27641
27642    cx.update_editor(|e, window, cx| e.paste(&Paste, window, cx));
27643
27644    cx.assert_editor_state("lineA\nline1\nline2\nline3\nˇ");
27645}
27646
27647#[gpui::test]
27648async fn test_end_of_editor_context(cx: &mut TestAppContext) {
27649    init_test(cx, |_| {});
27650
27651    let mut cx = EditorTestContext::new(cx).await;
27652
27653    cx.set_state("line1\nline2ˇ");
27654    cx.update_editor(|e, window, cx| {
27655        e.set_mode(EditorMode::SingleLine);
27656        assert!(e.key_context(window, cx).contains("end_of_input"));
27657    });
27658    cx.set_state("ˇline1\nline2");
27659    cx.update_editor(|e, window, cx| {
27660        assert!(!e.key_context(window, cx).contains("end_of_input"));
27661    });
27662    cx.set_state("line1ˇ\nline2");
27663    cx.update_editor(|e, window, cx| {
27664        assert!(!e.key_context(window, cx).contains("end_of_input"));
27665    });
27666}
27667
27668#[gpui::test]
27669async fn test_sticky_scroll(cx: &mut TestAppContext) {
27670    init_test(cx, |_| {});
27671    let mut cx = EditorTestContext::new(cx).await;
27672
27673    let buffer = indoc! {"
27674            ˇfn foo() {
27675                let abc = 123;
27676            }
27677            struct Bar;
27678            impl Bar {
27679                fn new() -> Self {
27680                    Self
27681                }
27682            }
27683            fn baz() {
27684            }
27685        "};
27686    cx.set_state(&buffer);
27687
27688    cx.update_editor(|e, _, cx| {
27689        e.buffer()
27690            .read(cx)
27691            .as_singleton()
27692            .unwrap()
27693            .update(cx, |buffer, cx| {
27694                buffer.set_language_immediate(Some(rust_lang()), cx);
27695            })
27696    });
27697
27698    let mut sticky_headers = |offset: ScrollOffset| {
27699        cx.update_editor(|e, window, cx| {
27700            e.scroll(gpui::Point { x: 0., y: offset }, None, window, cx);
27701            EditorElement::sticky_headers(&e, &e.snapshot(window, cx), cx)
27702                .into_iter()
27703                .map(
27704                    |StickyHeader {
27705                         start_point,
27706                         offset,
27707                         ..
27708                     }| { (start_point, offset) },
27709                )
27710                .collect::<Vec<_>>()
27711        })
27712    };
27713
27714    let fn_foo = Point { row: 0, column: 0 };
27715    let impl_bar = Point { row: 4, column: 0 };
27716    let fn_new = Point { row: 5, column: 4 };
27717
27718    assert_eq!(sticky_headers(0.0), vec![]);
27719    assert_eq!(sticky_headers(0.5), vec![(fn_foo, 0.0)]);
27720    assert_eq!(sticky_headers(1.0), vec![(fn_foo, 0.0)]);
27721    assert_eq!(sticky_headers(1.5), vec![(fn_foo, -0.5)]);
27722    assert_eq!(sticky_headers(2.0), vec![]);
27723    assert_eq!(sticky_headers(2.5), vec![]);
27724    assert_eq!(sticky_headers(3.0), vec![]);
27725    assert_eq!(sticky_headers(3.5), vec![]);
27726    assert_eq!(sticky_headers(4.0), vec![]);
27727    assert_eq!(sticky_headers(4.5), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27728    assert_eq!(sticky_headers(5.0), vec![(impl_bar, 0.0), (fn_new, 1.0)]);
27729    assert_eq!(sticky_headers(5.5), vec![(impl_bar, 0.0), (fn_new, 0.5)]);
27730    assert_eq!(sticky_headers(6.0), vec![(impl_bar, 0.0)]);
27731    assert_eq!(sticky_headers(6.5), vec![(impl_bar, 0.0)]);
27732    assert_eq!(sticky_headers(7.0), vec![(impl_bar, 0.0)]);
27733    assert_eq!(sticky_headers(7.5), vec![(impl_bar, -0.5)]);
27734    assert_eq!(sticky_headers(8.0), vec![]);
27735    assert_eq!(sticky_headers(8.5), vec![]);
27736    assert_eq!(sticky_headers(9.0), vec![]);
27737    assert_eq!(sticky_headers(9.5), vec![]);
27738    assert_eq!(sticky_headers(10.0), vec![]);
27739}
27740
27741#[gpui::test]
27742async fn test_scroll_by_clicking_sticky_header(cx: &mut TestAppContext) {
27743    init_test(cx, |_| {});
27744    cx.update(|cx| {
27745        SettingsStore::update_global(cx, |store, cx| {
27746            store.update_user_settings(cx, |settings| {
27747                settings.editor.sticky_scroll = Some(settings::StickyScrollContent {
27748                    enabled: Some(true),
27749                })
27750            });
27751        });
27752    });
27753    let mut cx = EditorTestContext::new(cx).await;
27754
27755    let line_height = cx.editor(|editor, window, _cx| {
27756        editor
27757            .style()
27758            .unwrap()
27759            .text
27760            .line_height_in_pixels(window.rem_size())
27761    });
27762
27763    let buffer = indoc! {"
27764            ˇfn foo() {
27765                let abc = 123;
27766            }
27767            struct Bar;
27768            impl Bar {
27769                fn new() -> Self {
27770                    Self
27771                }
27772            }
27773            fn baz() {
27774            }
27775        "};
27776    cx.set_state(&buffer);
27777
27778    cx.update_editor(|e, _, cx| {
27779        e.buffer()
27780            .read(cx)
27781            .as_singleton()
27782            .unwrap()
27783            .update(cx, |buffer, cx| {
27784                buffer.set_language_immediate(Some(rust_lang()), cx);
27785            })
27786    });
27787
27788    let fn_foo = || empty_range(0, 0);
27789    let impl_bar = || empty_range(4, 0);
27790    let fn_new = || empty_range(5, 4);
27791
27792    let mut scroll_and_click = |scroll_offset: ScrollOffset, click_offset: ScrollOffset| {
27793        cx.update_editor(|e, window, cx| {
27794            e.scroll(
27795                gpui::Point {
27796                    x: 0.,
27797                    y: scroll_offset,
27798                },
27799                None,
27800                window,
27801                cx,
27802            );
27803        });
27804        cx.simulate_click(
27805            gpui::Point {
27806                x: px(0.),
27807                y: click_offset as f32 * line_height,
27808            },
27809            Modifiers::none(),
27810        );
27811        cx.update_editor(|e, _, cx| (e.scroll_position(cx), display_ranges(e, cx)))
27812    };
27813
27814    assert_eq!(
27815        scroll_and_click(
27816            4.5, // impl Bar is halfway off the screen
27817            0.0  // click top of screen
27818        ),
27819        // scrolled to impl Bar
27820        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27821    );
27822
27823    assert_eq!(
27824        scroll_and_click(
27825            4.5,  // impl Bar is halfway off the screen
27826            0.25  // click middle of impl Bar
27827        ),
27828        // scrolled to impl Bar
27829        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27830    );
27831
27832    assert_eq!(
27833        scroll_and_click(
27834            4.5, // impl Bar is halfway off the screen
27835            1.5  // click below impl Bar (e.g. fn new())
27836        ),
27837        // scrolled to fn new() - this is below the impl Bar header which has persisted
27838        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27839    );
27840
27841    assert_eq!(
27842        scroll_and_click(
27843            5.5,  // fn new is halfway underneath impl Bar
27844            0.75  // click on the overlap of impl Bar and fn new()
27845        ),
27846        (gpui::Point { x: 0., y: 4. }, vec![impl_bar()])
27847    );
27848
27849    assert_eq!(
27850        scroll_and_click(
27851            5.5,  // fn new is halfway underneath impl Bar
27852            1.25  // click on the visible part of fn new()
27853        ),
27854        (gpui::Point { x: 0., y: 4. }, vec![fn_new()])
27855    );
27856
27857    assert_eq!(
27858        scroll_and_click(
27859            1.5, // fn foo is halfway off the screen
27860            0.0  // click top of screen
27861        ),
27862        (gpui::Point { x: 0., y: 0. }, vec![fn_foo()])
27863    );
27864
27865    assert_eq!(
27866        scroll_and_click(
27867            1.5,  // fn foo is halfway off the screen
27868            0.75  // click visible part of let abc...
27869        )
27870        .0,
27871        // no change in scroll
27872        // we don't assert on the visible_range because if we clicked the gutter, our line is fully selected
27873        (gpui::Point { x: 0., y: 1.5 })
27874    );
27875}
27876
27877#[gpui::test]
27878async fn test_next_prev_reference(cx: &mut TestAppContext) {
27879    const CYCLE_POSITIONS: &[&'static str] = &[
27880        indoc! {"
27881            fn foo() {
27882                let ˇabc = 123;
27883                let x = abc + 1;
27884                let y = abc + 2;
27885                let z = abc + 2;
27886            }
27887        "},
27888        indoc! {"
27889            fn foo() {
27890                let abc = 123;
27891                let x = ˇabc + 1;
27892                let y = abc + 2;
27893                let z = abc + 2;
27894            }
27895        "},
27896        indoc! {"
27897            fn foo() {
27898                let abc = 123;
27899                let x = abc + 1;
27900                let y = ˇabc + 2;
27901                let z = abc + 2;
27902            }
27903        "},
27904        indoc! {"
27905            fn foo() {
27906                let abc = 123;
27907                let x = abc + 1;
27908                let y = abc + 2;
27909                let z = ˇabc + 2;
27910            }
27911        "},
27912    ];
27913
27914    init_test(cx, |_| {});
27915
27916    let mut cx = EditorLspTestContext::new_rust(
27917        lsp::ServerCapabilities {
27918            references_provider: Some(lsp::OneOf::Left(true)),
27919            ..Default::default()
27920        },
27921        cx,
27922    )
27923    .await;
27924
27925    // importantly, the cursor is in the middle
27926    cx.set_state(indoc! {"
27927        fn foo() {
27928            let aˇbc = 123;
27929            let x = abc + 1;
27930            let y = abc + 2;
27931            let z = abc + 2;
27932        }
27933    "});
27934
27935    let reference_ranges = [
27936        lsp::Position::new(1, 8),
27937        lsp::Position::new(2, 12),
27938        lsp::Position::new(3, 12),
27939        lsp::Position::new(4, 12),
27940    ]
27941    .map(|start| lsp::Range::new(start, lsp::Position::new(start.line, start.character + 3)));
27942
27943    cx.lsp
27944        .set_request_handler::<lsp::request::References, _, _>(move |params, _cx| async move {
27945            Ok(Some(
27946                reference_ranges
27947                    .map(|range| lsp::Location {
27948                        uri: params.text_document_position.text_document.uri.clone(),
27949                        range,
27950                    })
27951                    .to_vec(),
27952            ))
27953        });
27954
27955    let _move = async |direction, count, cx: &mut EditorLspTestContext| {
27956        cx.update_editor(|editor, window, cx| {
27957            editor.go_to_reference_before_or_after_position(direction, count, window, cx)
27958        })
27959        .unwrap()
27960        .await
27961        .unwrap()
27962    };
27963
27964    _move(Direction::Next, 1, &mut cx).await;
27965    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27966
27967    _move(Direction::Next, 1, &mut cx).await;
27968    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27969
27970    _move(Direction::Next, 1, &mut cx).await;
27971    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27972
27973    // loops back to the start
27974    _move(Direction::Next, 1, &mut cx).await;
27975    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27976
27977    // loops back to the end
27978    _move(Direction::Prev, 1, &mut cx).await;
27979    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27980
27981    _move(Direction::Prev, 1, &mut cx).await;
27982    cx.assert_editor_state(CYCLE_POSITIONS[2]);
27983
27984    _move(Direction::Prev, 1, &mut cx).await;
27985    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27986
27987    _move(Direction::Prev, 1, &mut cx).await;
27988    cx.assert_editor_state(CYCLE_POSITIONS[0]);
27989
27990    _move(Direction::Next, 3, &mut cx).await;
27991    cx.assert_editor_state(CYCLE_POSITIONS[3]);
27992
27993    _move(Direction::Prev, 2, &mut cx).await;
27994    cx.assert_editor_state(CYCLE_POSITIONS[1]);
27995}
27996
27997#[gpui::test]
27998async fn test_multibuffer_selections_with_folding(cx: &mut TestAppContext) {
27999    init_test(cx, |_| {});
28000
28001    let (editor, cx) = cx.add_window_view(|window, cx| {
28002        let multi_buffer = MultiBuffer::build_multi(
28003            [
28004                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28005                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28006            ],
28007            cx,
28008        );
28009        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28010    });
28011
28012    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28013    let buffer_ids = cx.multibuffer(|mb, _| mb.excerpt_buffer_ids());
28014
28015    cx.assert_excerpts_with_selections(indoc! {"
28016        [EXCERPT]
28017        ˇ1
28018        2
28019        3
28020        [EXCERPT]
28021        1
28022        2
28023        3
28024        "});
28025
28026    // Scenario 1: Unfolded buffers, position cursor on "2", select all matches, then insert
28027    cx.update_editor(|editor, window, cx| {
28028        editor.change_selections(None.into(), window, cx, |s| {
28029            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28030        });
28031    });
28032    cx.assert_excerpts_with_selections(indoc! {"
28033        [EXCERPT]
28034        1
2803528036        3
28037        [EXCERPT]
28038        1
28039        2
28040        3
28041        "});
28042
28043    cx.update_editor(|editor, window, cx| {
28044        editor
28045            .select_all_matches(&SelectAllMatches, window, cx)
28046            .unwrap();
28047    });
28048    cx.assert_excerpts_with_selections(indoc! {"
28049        [EXCERPT]
28050        1
2805128052        3
28053        [EXCERPT]
28054        1
2805528056        3
28057        "});
28058
28059    cx.update_editor(|editor, window, cx| {
28060        editor.handle_input("X", window, cx);
28061    });
28062    cx.assert_excerpts_with_selections(indoc! {"
28063        [EXCERPT]
28064        1
2806528066        3
28067        [EXCERPT]
28068        1
2806928070        3
28071        "});
28072
28073    // Scenario 2: Select "2", then fold second buffer before insertion
28074    cx.update_multibuffer(|mb, cx| {
28075        for buffer_id in buffer_ids.iter() {
28076            let buffer = mb.buffer(*buffer_id).unwrap();
28077            buffer.update(cx, |buffer, cx| {
28078                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28079            });
28080        }
28081    });
28082
28083    // Select "2" and select all matches
28084    cx.update_editor(|editor, window, cx| {
28085        editor.change_selections(None.into(), window, cx, |s| {
28086            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28087        });
28088        editor
28089            .select_all_matches(&SelectAllMatches, window, cx)
28090            .unwrap();
28091    });
28092
28093    // Fold second buffer - should remove selections from folded buffer
28094    cx.update_editor(|editor, _, cx| {
28095        editor.fold_buffer(buffer_ids[1], cx);
28096    });
28097    cx.assert_excerpts_with_selections(indoc! {"
28098        [EXCERPT]
28099        1
2810028101        3
28102        [EXCERPT]
28103        [FOLDED]
28104        "});
28105
28106    // Insert text - should only affect first buffer
28107    cx.update_editor(|editor, window, cx| {
28108        editor.handle_input("Y", window, cx);
28109    });
28110    cx.update_editor(|editor, _, cx| {
28111        editor.unfold_buffer(buffer_ids[1], cx);
28112    });
28113    cx.assert_excerpts_with_selections(indoc! {"
28114        [EXCERPT]
28115        1
2811628117        3
28118        [EXCERPT]
28119        1
28120        2
28121        3
28122        "});
28123
28124    // Scenario 3: Select "2", then fold first buffer before insertion
28125    cx.update_multibuffer(|mb, cx| {
28126        for buffer_id in buffer_ids.iter() {
28127            let buffer = mb.buffer(*buffer_id).unwrap();
28128            buffer.update(cx, |buffer, cx| {
28129                buffer.edit([(0..buffer.len(), "1\n2\n3\n")], None, cx);
28130            });
28131        }
28132    });
28133
28134    // Select "2" and select all matches
28135    cx.update_editor(|editor, window, cx| {
28136        editor.change_selections(None.into(), window, cx, |s| {
28137            s.select_ranges([MultiBufferOffset(2)..MultiBufferOffset(3)]);
28138        });
28139        editor
28140            .select_all_matches(&SelectAllMatches, window, cx)
28141            .unwrap();
28142    });
28143
28144    // Fold first buffer - should remove selections from folded buffer
28145    cx.update_editor(|editor, _, cx| {
28146        editor.fold_buffer(buffer_ids[0], cx);
28147    });
28148    cx.assert_excerpts_with_selections(indoc! {"
28149        [EXCERPT]
28150        [FOLDED]
28151        [EXCERPT]
28152        1
2815328154        3
28155        "});
28156
28157    // Insert text - should only affect second buffer
28158    cx.update_editor(|editor, window, cx| {
28159        editor.handle_input("Z", window, cx);
28160    });
28161    cx.update_editor(|editor, _, cx| {
28162        editor.unfold_buffer(buffer_ids[0], cx);
28163    });
28164    cx.assert_excerpts_with_selections(indoc! {"
28165        [EXCERPT]
28166        1
28167        2
28168        3
28169        [EXCERPT]
28170        1
2817128172        3
28173        "});
28174
28175    // Edge case scenario: fold all buffers, then try to insert
28176    cx.update_editor(|editor, _, cx| {
28177        editor.fold_buffer(buffer_ids[0], cx);
28178        editor.fold_buffer(buffer_ids[1], cx);
28179    });
28180    cx.assert_excerpts_with_selections(indoc! {"
28181        [EXCERPT]
28182        ˇ[FOLDED]
28183        [EXCERPT]
28184        [FOLDED]
28185        "});
28186
28187    // Insert should work via default selection
28188    cx.update_editor(|editor, window, cx| {
28189        editor.handle_input("W", window, cx);
28190    });
28191    cx.update_editor(|editor, _, cx| {
28192        editor.unfold_buffer(buffer_ids[0], cx);
28193        editor.unfold_buffer(buffer_ids[1], cx);
28194    });
28195    cx.assert_excerpts_with_selections(indoc! {"
28196        [EXCERPT]
28197        Wˇ1
28198        2
28199        3
28200        [EXCERPT]
28201        1
28202        Z
28203        3
28204        "});
28205}
28206
28207#[gpui::test]
28208async fn test_filtered_editor_pair(cx: &mut gpui::TestAppContext) {
28209    init_test(cx, |_| {});
28210    let mut leader_cx = EditorTestContext::new(cx).await;
28211
28212    let diff_base = indoc!(
28213        r#"
28214        one
28215        two
28216        three
28217        four
28218        five
28219        six
28220        "#
28221    );
28222
28223    let initial_state = indoc!(
28224        r#"
28225        ˇone
28226        two
28227        THREE
28228        four
28229        five
28230        six
28231        "#
28232    );
28233
28234    leader_cx.set_state(initial_state);
28235
28236    leader_cx.set_head_text(&diff_base);
28237    leader_cx.run_until_parked();
28238
28239    let follower = leader_cx.update_multibuffer(|leader, cx| {
28240        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28241        leader.set_all_diff_hunks_expanded(cx);
28242        leader.get_or_create_follower(cx)
28243    });
28244    follower.update(cx, |follower, cx| {
28245        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28246        follower.set_all_diff_hunks_expanded(cx);
28247    });
28248
28249    let follower_editor =
28250        leader_cx.new_window_entity(|window, cx| build_editor(follower, window, cx));
28251    // leader_cx.window.focus(&follower_editor.focus_handle(cx));
28252
28253    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor, &mut leader_cx).await;
28254    cx.run_until_parked();
28255
28256    leader_cx.assert_editor_state(initial_state);
28257    follower_cx.assert_editor_state(indoc! {
28258        r#"
28259        ˇone
28260        two
28261        three
28262        four
28263        five
28264        six
28265        "#
28266    });
28267
28268    follower_cx.editor(|editor, _window, cx| {
28269        assert!(editor.read_only(cx));
28270    });
28271
28272    leader_cx.update_editor(|editor, _window, cx| {
28273        editor.edit([(Point::new(4, 0)..Point::new(5, 0), "FIVE\n")], cx);
28274    });
28275    cx.run_until_parked();
28276
28277    leader_cx.assert_editor_state(indoc! {
28278        r#"
28279        ˇone
28280        two
28281        THREE
28282        four
28283        FIVE
28284        six
28285        "#
28286    });
28287
28288    follower_cx.assert_editor_state(indoc! {
28289        r#"
28290        ˇone
28291        two
28292        three
28293        four
28294        five
28295        six
28296        "#
28297    });
28298
28299    leader_cx.update_editor(|editor, _window, cx| {
28300        editor.edit([(Point::new(6, 0)..Point::new(6, 0), "SEVEN")], cx);
28301    });
28302    cx.run_until_parked();
28303
28304    leader_cx.assert_editor_state(indoc! {
28305        r#"
28306        ˇone
28307        two
28308        THREE
28309        four
28310        FIVE
28311        six
28312        SEVEN"#
28313    });
28314
28315    follower_cx.assert_editor_state(indoc! {
28316        r#"
28317        ˇone
28318        two
28319        three
28320        four
28321        five
28322        six
28323        "#
28324    });
28325
28326    leader_cx.update_editor(|editor, window, cx| {
28327        editor.move_down(&MoveDown, window, cx);
28328        editor.refresh_selected_text_highlights(true, window, cx);
28329    });
28330    leader_cx.run_until_parked();
28331}
28332
28333#[gpui::test]
28334async fn test_filtered_editor_pair_complex(cx: &mut gpui::TestAppContext) {
28335    init_test(cx, |_| {});
28336    let base_text = "base\n";
28337    let buffer_text = "buffer\n";
28338
28339    let buffer1 = cx.new(|cx| Buffer::local(buffer_text, cx));
28340    let diff1 = cx.new(|cx| BufferDiff::new_with_base_text(base_text, &buffer1, cx));
28341
28342    let extra_buffer_1 = cx.new(|cx| Buffer::local("dummy text 1\n", cx));
28343    let extra_diff_1 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_1, cx));
28344    let extra_buffer_2 = cx.new(|cx| Buffer::local("dummy text 2\n", cx));
28345    let extra_diff_2 = cx.new(|cx| BufferDiff::new_with_base_text("", &extra_buffer_2, cx));
28346
28347    let leader = cx.new(|cx| {
28348        let mut leader = MultiBuffer::new(Capability::ReadWrite);
28349        leader.set_all_diff_hunks_expanded(cx);
28350        leader.set_filter_mode(Some(MultiBufferFilterMode::KeepInsertions));
28351        leader
28352    });
28353    let follower = leader.update(cx, |leader, cx| leader.get_or_create_follower(cx));
28354    follower.update(cx, |follower, _| {
28355        follower.set_filter_mode(Some(MultiBufferFilterMode::KeepDeletions));
28356    });
28357
28358    leader.update(cx, |leader, cx| {
28359        leader.insert_excerpts_after(
28360            ExcerptId::min(),
28361            extra_buffer_2.clone(),
28362            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28363            cx,
28364        );
28365        leader.add_diff(extra_diff_2.clone(), cx);
28366
28367        leader.insert_excerpts_after(
28368            ExcerptId::min(),
28369            extra_buffer_1.clone(),
28370            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28371            cx,
28372        );
28373        leader.add_diff(extra_diff_1.clone(), cx);
28374
28375        leader.insert_excerpts_after(
28376            ExcerptId::min(),
28377            buffer1.clone(),
28378            vec![ExcerptRange::new(text::Anchor::MIN..text::Anchor::MAX)],
28379            cx,
28380        );
28381        leader.add_diff(diff1.clone(), cx);
28382    });
28383
28384    cx.run_until_parked();
28385    let mut cx = cx.add_empty_window();
28386
28387    let leader_editor = cx
28388        .new_window_entity(|window, cx| Editor::for_multibuffer(leader.clone(), None, window, cx));
28389    let follower_editor = cx.new_window_entity(|window, cx| {
28390        Editor::for_multibuffer(follower.clone(), None, window, cx)
28391    });
28392
28393    let mut leader_cx = EditorTestContext::for_editor_in(leader_editor.clone(), &mut cx).await;
28394    leader_cx.assert_editor_state(indoc! {"
28395       ˇbuffer
28396
28397       dummy text 1
28398
28399       dummy text 2
28400    "});
28401    let mut follower_cx = EditorTestContext::for_editor_in(follower_editor.clone(), &mut cx).await;
28402    follower_cx.assert_editor_state(indoc! {"
28403        ˇbase
28404
28405
28406    "});
28407}
28408
28409#[gpui::test]
28410async fn test_multibuffer_scroll_cursor_top_margin(cx: &mut TestAppContext) {
28411    init_test(cx, |_| {});
28412
28413    let (editor, cx) = cx.add_window_view(|window, cx| {
28414        let multi_buffer = MultiBuffer::build_multi(
28415            [
28416                ("1\n2\n3\n", vec![Point::row_range(0..3)]),
28417                ("1\n2\n3\n4\n5\n6\n7\n8\n9\n", vec![Point::row_range(0..9)]),
28418            ],
28419            cx,
28420        );
28421        Editor::new(EditorMode::full(), multi_buffer, None, window, cx)
28422    });
28423
28424    let mut cx = EditorTestContext::for_editor_in(editor.clone(), cx).await;
28425
28426    cx.assert_excerpts_with_selections(indoc! {"
28427        [EXCERPT]
28428        ˇ1
28429        2
28430        3
28431        [EXCERPT]
28432        1
28433        2
28434        3
28435        4
28436        5
28437        6
28438        7
28439        8
28440        9
28441        "});
28442
28443    cx.update_editor(|editor, window, cx| {
28444        editor.change_selections(None.into(), window, cx, |s| {
28445            s.select_ranges([MultiBufferOffset(19)..MultiBufferOffset(19)]);
28446        });
28447    });
28448
28449    cx.assert_excerpts_with_selections(indoc! {"
28450        [EXCERPT]
28451        1
28452        2
28453        3
28454        [EXCERPT]
28455        1
28456        2
28457        3
28458        4
28459        5
28460        6
28461        ˇ7
28462        8
28463        9
28464        "});
28465
28466    cx.update_editor(|editor, _window, cx| {
28467        editor.set_vertical_scroll_margin(0, cx);
28468    });
28469
28470    cx.update_editor(|editor, window, cx| {
28471        assert_eq!(editor.vertical_scroll_margin(), 0);
28472        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28473        assert_eq!(
28474            editor.snapshot(window, cx).scroll_position(),
28475            gpui::Point::new(0., 12.0)
28476        );
28477    });
28478
28479    cx.update_editor(|editor, _window, cx| {
28480        editor.set_vertical_scroll_margin(3, cx);
28481    });
28482
28483    cx.update_editor(|editor, window, cx| {
28484        assert_eq!(editor.vertical_scroll_margin(), 3);
28485        editor.scroll_cursor_top(&ScrollCursorTop, window, cx);
28486        assert_eq!(
28487            editor.snapshot(window, cx).scroll_position(),
28488            gpui::Point::new(0., 9.0)
28489        );
28490    });
28491}